@xopcai/xopc 0.0.283 → 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-iPtG94qr.js → activity-store-B0BTIk9j.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DQ74xqLi.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-KMJzMqvZ.js → api-Coly9ab6.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-B42TpqIb.js → app-management-settings-panel-TWBO-msg.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-D9Lfe7Sk.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-BdmQwj4x.js → archive-plugin-z_u6alob.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-B4S0_nji.js → attachment-load-C2nS04TC.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-B3TmHNjQ.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-B3cXF_DP.js → binary-plugins-CGmXoZnl.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-05zyBXBe.js → block-editor-CtvzCZhC.js} +3 -3
- package/dist/gateway/static/root/assets/{browser-automation-inputs-BTIufVbM.js → browser-automation-inputs-BOTU1isH.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-DRBA9nwe.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-DMYQNs-J.js → channels-status-swr-Dfi0Desh.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-Dx5KQyo5.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-CkDROZoe.js → date-picker-DXOvbHCr.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-Bl8HcvzL.js → dependency-picker-BEnC-eS0.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-BcknAjbW.js → desktop-pet-DtPNvznL.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-BuCpXQPm.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-qRpb2FFa.js → extension-debug-page-H_aPMGXW.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-C7gs9wu4.js → extension-page-C1JnpeMt.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CyJP1CMZ.js → extension-settings-page-C75kCOqe.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-Cpmyrr6j.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-DoiPeb65.js → gateway-startup-retry-GP31PUrG.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-CV6Dolcn.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-CqjHWl2T.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-CHgOG1aR.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-B3XqA41D.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-BcIVYp0J.js → local-apps-page-BtgDJ92Q.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-1urrBBCQ.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-DNFCdgzR.js → management-settings-DasZxCyD.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-view-BZmcFIWa.js → markdown-view-6oYdBahT.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-BDxNhoBC.js → media-plugins-CxthbBvk.js} +1 -1
- package/dist/gateway/static/root/assets/messages-Ls1Aaiqd.js +3 -0
- 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-agH3a2PI.js → page-tabs-BG39KidE.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-XEZ7zDOP.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-lXAbj85Z.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-CxQstD00.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-DErKZlVJ.js → runtime-tools-settings-panel-Wkru8_My.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-DhCmJQVJ.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-Dg6chotg.js → settings-form-section-wcAdCkK8.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-LyuN5N3M.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-CZvmf4Ob.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-AmqtBJo7.js +2 -0
- package/dist/gateway/static/root/assets/{skills-page.utils-Eu0WVHTS.js → skills-page.utils-DtlfCgJp.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-DQKkM7B2.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-Dc5s20V3.js → theme-store-ng8KJsAT.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-TKQpPVd6.js → tunnel-api-Boajol0M.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-CkVlrFk1.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-B12fFN-L.js → voice-api-key-field-SE3OQI1s.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-Mk78BrpP.js → voice-config-api-VNuCmdFe.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-CT9Zy36i.js → work-discovery-overlay-Dn1vy2oq.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BwefQliE.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-loaders.js +1 -0
- package/dist/src/cli/command-manifest.js +4 -0
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
- package/dist/src/cli/commands/gateway/run-foreground.js +3 -0
- package/dist/src/cli/commands/import.d.ts +2 -0
- package/dist/src/cli/commands/import.js +78 -0
- 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 +61 -44
- package/dist/src/gateway/hono/routes/discussions.js +2 -2
- package/dist/src/gateway/hono/routes/imports.d.ts +3 -0
- package/dist/src/gateway/hono/routes/imports.js +56 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
- package/dist/src/gateway/hono/routes/proactive-control.js +2 -1
- package/dist/src/gateway/service.d.ts +7 -0
- package/dist/src/gateway/service.js +26 -9
- package/dist/src/imports/compatibility.d.ts +7 -0
- package/dist/src/imports/compatibility.js +174 -0
- package/dist/src/imports/contextImport.d.ts +3 -0
- package/dist/src/imports/contextImport.js +56 -0
- package/dist/src/imports/files.d.ts +13 -0
- package/dist/src/imports/files.js +77 -0
- package/dist/src/imports/importService.d.ts +50 -0
- package/dist/src/imports/importService.js +414 -0
- package/dist/src/imports/inventory.d.ts +11 -0
- package/dist/src/imports/inventory.js +202 -0
- package/dist/src/imports/planner.d.ts +11 -0
- package/dist/src/imports/planner.js +98 -0
- package/dist/src/imports/productImport.d.ts +10 -0
- package/dist/src/imports/productImport.js +241 -0
- package/dist/src/imports/runtime.d.ts +5 -0
- package/dist/src/imports/runtime.js +61 -0
- package/dist/src/imports/scanner.d.ts +14 -0
- package/dist/src/imports/scanner.js +159 -0
- package/dist/src/imports/secrets.d.ts +2 -0
- package/dist/src/imports/secrets.js +9 -0
- package/dist/src/imports/sources.d.ts +37 -0
- package/dist/src/imports/sources.js +75 -0
- package/dist/src/imports/types.d.ts +144 -0
- package/dist/src/imports/types.js +11 -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/knowledge-memory/repository.js +4 -2
- 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 +18 -0
- package/dist/src/storage/sqlite/import-repository.js +35 -0
- 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/174_capability_imports.sql +11 -0
- 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 +4 -3
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-SElFkQd6.js +0 -1
- package/dist/gateway/static/root/assets/agents-DYazUaZ6.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-BzrodN8C.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace--A4I0jMu.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-DDTXUNWK.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-Ll5UeXq5.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-BzL15bkU.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-RPw1AOMb.js +0 -2
- package/dist/gateway/static/root/assets/device-pairing-wizard-D0_tCX49.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-DOeeTdWE.js +0 -1
- package/dist/gateway/static/root/assets/home-page-Djw1H_Go.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-BPqgyYJq.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-BHax3XlS.js +0 -2
- package/dist/gateway/static/root/assets/messages-DYFcjtmG.js +0 -3
- package/dist/gateway/static/root/assets/note-detail-panel-Cs46LFXO.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-BUkcn_0t.js +0 -8
- package/dist/gateway/static/root/assets/proactive-page-Bn484J9v.js +0 -1
- package/dist/gateway/static/root/assets/project-detail-page-LcH21ixD.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-DelBiwyB.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-3BjFYXCf.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-CVFirgVd.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DyfBpqkY.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-CrXhTzHr.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-DyM_D8hM.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-Bys58-15.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-sWNvFo6D.js +0 -2
- package/dist/gateway/static/root/assets/task-detail-page-B18NcZQJ.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-ul-zr58b.js +0 -2
- package/dist/gateway/static/root/assets/workflows-page-BY_AWs7S.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 };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ImportAction, type ImportPlan, type ImportScan, type ImportTarget } from './types.js';
|
|
2
|
+
export declare function targetInventory(root: string): Array<{
|
|
3
|
+
directory: string;
|
|
4
|
+
name: string;
|
|
5
|
+
hash: string;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function targetRevision(root: string): string;
|
|
8
|
+
export declare function canonicalTarget(root: string): string;
|
|
9
|
+
export declare function createImportPlan(scan: ImportScan, target: ImportTarget, choices: Array<Pick<ImportAction, 'candidateId' | 'operation'> & {
|
|
10
|
+
name?: string;
|
|
11
|
+
}>, reservedNames?: string[]): ImportPlan;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { isValidSkillId } from "../agent/skills/managed-store.js";
|
|
2
|
+
import { ImportError } from "./types.js";
|
|
3
|
+
import { IMPORT_LIMITS, digest, fileHash, readTree } from "./files.js";
|
|
4
|
+
import { parseSkill } from "./compatibility.js";
|
|
5
|
+
import { join, relative, resolve } from "node:path";
|
|
6
|
+
import { existsSync, readdirSync, realpathSync } from "node:fs";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
//#region src/imports/planner.ts
|
|
9
|
+
function targetInventory(root) {
|
|
10
|
+
if (!existsSync(root)) return [];
|
|
11
|
+
let bytes = 0;
|
|
12
|
+
let count = 0;
|
|
13
|
+
return readdirSync(root, { withFileTypes: true }).filter((e) => !e.name.startsWith(".")).map((entry) => {
|
|
14
|
+
if (!entry.isDirectory() || entry.isSymbolicLink()) throw new ImportError("unsafe_target", "Target contains a link or non-directory item", 409);
|
|
15
|
+
const files = readTree(join(root, entry.name));
|
|
16
|
+
bytes += files.reduce((n, f) => n + Buffer.byteLength(f.data, "base64"), 0);
|
|
17
|
+
count += files.length;
|
|
18
|
+
if (bytes > IMPORT_LIMITS.total || count > IMPORT_LIMITS.count) throw new ImportError("limit_exceeded", "Target inventory exceeds import limits", 413);
|
|
19
|
+
const primary = files.find((f) => f.path === "SKILL.md");
|
|
20
|
+
let name = entry.name;
|
|
21
|
+
try {
|
|
22
|
+
if (primary) name = String(parseSkill(Buffer.from(primary.data, "base64").toString("utf8")).metadata.name);
|
|
23
|
+
} catch {}
|
|
24
|
+
return {
|
|
25
|
+
directory: entry.name,
|
|
26
|
+
name,
|
|
27
|
+
hash: fileHash(files)
|
|
28
|
+
};
|
|
29
|
+
}).sort((a, b) => a.directory.localeCompare(b.directory));
|
|
30
|
+
}
|
|
31
|
+
function targetRevision(root) {
|
|
32
|
+
return digest(targetInventory(root));
|
|
33
|
+
}
|
|
34
|
+
function canonicalTarget(root) {
|
|
35
|
+
let current = resolve(root);
|
|
36
|
+
while (!existsSync(current)) {
|
|
37
|
+
const parent = resolve(current, "..");
|
|
38
|
+
if (parent === current) break;
|
|
39
|
+
current = parent;
|
|
40
|
+
}
|
|
41
|
+
return join(realpathSync(current), relative(current, resolve(root)));
|
|
42
|
+
}
|
|
43
|
+
function createImportPlan(scan, target, choices, reservedNames = []) {
|
|
44
|
+
if (Date.now() - scan.createdAt > 1440 * 60 * 1e3) throw new ImportError("plan_stale", "Scan expired; scan again", 409);
|
|
45
|
+
target = {
|
|
46
|
+
...target,
|
|
47
|
+
root: canonicalTarget(target.root)
|
|
48
|
+
};
|
|
49
|
+
const inventory = targetInventory(target.root);
|
|
50
|
+
const used = /* @__PURE__ */ new Set();
|
|
51
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
52
|
+
const normalize = (s) => s.normalize("NFC").toLowerCase();
|
|
53
|
+
const actions = choices.map((choice) => {
|
|
54
|
+
const item = scan.candidates.find((c) => c.id === choice.candidateId);
|
|
55
|
+
if (!item || candidates.has(item.id)) throw new ImportError("invalid_selection", "Invalid or duplicate candidate");
|
|
56
|
+
candidates.add(item.id);
|
|
57
|
+
const name = choice.name ?? item.name;
|
|
58
|
+
if (!isValidSkillId(name)) throw new ImportError("invalid_name", "Choose a name using letters, numbers, dot, dash or underscore");
|
|
59
|
+
if (choice.operation !== "skip" && !["compatible", "needs_setup"].includes(item.compatibility)) throw new ImportError("incompatible", "Selected item requires manual adaptation");
|
|
60
|
+
if (item.scope === "project" && !target.projectId && choice.operation !== "skip") throw new ImportError("scope_mismatch", "Project capabilities must remain project-scoped");
|
|
61
|
+
if (item.kind !== "skill" && choice.operation !== "skip" && choice.operation !== "create") throw new ImportError("invalid_operation", "MCP drafts only support create");
|
|
62
|
+
const existing = item.kind === "skill" ? inventory.find((e) => normalize(e.directory) === normalize(name) || normalize(e.name) === normalize(name)) : void 0;
|
|
63
|
+
const hash = existing?.hash ?? null;
|
|
64
|
+
if (choice.operation !== "skip") {
|
|
65
|
+
const key = `${item.kind}:${normalize(name)}`;
|
|
66
|
+
if (used.has(key)) throw new ImportError("conflict", "Two selected items have the same target name", 409);
|
|
67
|
+
used.add(key);
|
|
68
|
+
if (existing?.hash === item.hash && choice.operation === "create") return {
|
|
69
|
+
candidateId: item.id,
|
|
70
|
+
operation: "skip",
|
|
71
|
+
name,
|
|
72
|
+
beforeHash: hash
|
|
73
|
+
};
|
|
74
|
+
if (choice.operation === "replace") {
|
|
75
|
+
if (!existing || existing.directory !== name || existing.name !== name) throw new ImportError("conflict", "Replace requires an exact managed directory and skill name match", 409);
|
|
76
|
+
} else if (existing || item.kind === "skill" && reservedNames.some((n) => normalize(n) === normalize(name))) throw new ImportError("conflict", "Name is already used; choose another name or explicitly replace a managed skill", 409);
|
|
77
|
+
if (choice.operation === "create" && name !== item.name) throw new ImportError("invalid_operation", "Use rename when changing the skill name");
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
candidateId: item.id,
|
|
81
|
+
operation: choice.operation,
|
|
82
|
+
name,
|
|
83
|
+
beforeHash: hash
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
if (!actions.length) throw new ImportError("invalid_selection", "Select at least one item");
|
|
87
|
+
return {
|
|
88
|
+
id: randomUUID(),
|
|
89
|
+
scanId: scan.id,
|
|
90
|
+
createdAt: Date.now(),
|
|
91
|
+
expiresAt: Date.now() + 1440 * 60 * 1e3,
|
|
92
|
+
target,
|
|
93
|
+
targetRevision: digest(inventory),
|
|
94
|
+
actions
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { canonicalTarget, createImportPlan, targetInventory, targetRevision };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ImportService } from './importService.js';
|
|
2
|
+
import { type ImportSelection, type ProductImportResult } from './types.js';
|
|
3
|
+
export declare function listImportSources(owner: string): {
|
|
4
|
+
lastImport: ProductImportResult;
|
|
5
|
+
id: import("./types.js").ImportSource;
|
|
6
|
+
name: string;
|
|
7
|
+
detected: boolean;
|
|
8
|
+
}[];
|
|
9
|
+
export declare function importSelection(service: ImportService, selection: ImportSelection, owner?: string): Promise<ProductImportResult>;
|
|
10
|
+
export declare function recoverSelectionRuns(service: ImportService, owner: string): Promise<void>;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { resolveStateDir } from "../config/paths-state.js";
|
|
2
|
+
import { runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
|
|
3
|
+
import { init_paths } from "../config/paths.js";
|
|
4
|
+
import { ProjectService } from "../projects/project-service.js";
|
|
5
|
+
import { ImportError } from "./types.js";
|
|
6
|
+
import { digest, fileHash, readRegularFile, readTree } from "./files.js";
|
|
7
|
+
import { importContextDocument } from "./contextImport.js";
|
|
8
|
+
import { canonicalTarget, targetInventory } from "./planner.js";
|
|
9
|
+
import { detectImportSources } from "./sources.js";
|
|
10
|
+
import { ImportSelectionRepository } from "../storage/sqlite/import-selection-repository.js";
|
|
11
|
+
import { existingContext, getLiveInventory } from "./inventory.js";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { mkdirSync, realpathSync, statSync } from "node:fs";
|
|
14
|
+
import { setImmediate } from "node:timers/promises";
|
|
15
|
+
import lockfile from "proper-lockfile";
|
|
16
|
+
//#region src/imports/productImport.ts
|
|
17
|
+
init_paths();
|
|
18
|
+
function listImportSources(owner) {
|
|
19
|
+
const runs = new ImportSelectionRepository(owner).list("run");
|
|
20
|
+
return detectImportSources().map((source) => ({
|
|
21
|
+
...source,
|
|
22
|
+
lastImport: runs.find((r) => r.source === source.id)
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
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, {
|
|
160
|
+
recursive: true,
|
|
161
|
+
mode: 448
|
|
162
|
+
});
|
|
163
|
+
const release = await lockfile.lock(path, {
|
|
164
|
+
realpath: false,
|
|
165
|
+
stale: 6e4,
|
|
166
|
+
retries: {
|
|
167
|
+
retries: 15,
|
|
168
|
+
minTimeout: 100,
|
|
169
|
+
maxTimeout: 500
|
|
170
|
+
}
|
|
171
|
+
});
|
|
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;
|
|
187
|
+
try {
|
|
188
|
+
previous = repo.get("run", selection.requestId);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (!(error instanceof ImportError) || error.code !== "not_found") throw error;
|
|
191
|
+
}
|
|
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,
|
|
211
|
+
createdAt: Date.now(),
|
|
212
|
+
status: "running",
|
|
213
|
+
skills: 0,
|
|
214
|
+
context: 0,
|
|
215
|
+
projects: 0,
|
|
216
|
+
skipped: 0,
|
|
217
|
+
issues: [],
|
|
218
|
+
items: items.map((item) => ({
|
|
219
|
+
candidateId: item.id,
|
|
220
|
+
kind: item.kind,
|
|
221
|
+
name: item.name,
|
|
222
|
+
status: "pending"
|
|
223
|
+
}))
|
|
224
|
+
};
|
|
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);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
241
|
+
export { importSelection, listImportSources, recoverSelectionRuns };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ImportService } from './importService.js';
|
|
2
|
+
import { type ImportTarget } from './types.js';
|
|
3
|
+
export declare function resolveImportTarget(projectId?: string): ImportTarget;
|
|
4
|
+
export declare function createRuntimeImportService(owner: string, refreshSkills?: () => void | Promise<void>, checkTrust?: (root: string) => boolean): ImportService;
|
|
5
|
+
export declare function recoverCapabilityImports(): Promise<void>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { resolveStateDir } from "../config/paths-state.js";
|
|
2
|
+
import { init_paths, resolveBundledSkillsDir, resolveSkillsDir } from "../config/paths.js";
|
|
3
|
+
import { ProjectService } from "../projects/project-service.js";
|
|
4
|
+
import { resolveWorkspaceSkillsDir } from "../agent/skills/workspace-skills-dir.js";
|
|
5
|
+
import { loadSkills } from "../agent/skills/index.js";
|
|
6
|
+
import { ProjectTrustStore } from "../project-trust/trust-store.js";
|
|
7
|
+
import { ImportError } from "./types.js";
|
|
8
|
+
import { ImportRepository } from "../storage/sqlite/import-repository.js";
|
|
9
|
+
import { canonicalTarget } from "./planner.js";
|
|
10
|
+
import { ImportService } from "./importService.js";
|
|
11
|
+
import { ImportSelectionRepository } from "../storage/sqlite/import-selection-repository.js";
|
|
12
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
13
|
+
//#region src/imports/runtime.ts
|
|
14
|
+
init_paths();
|
|
15
|
+
function resolveImportTarget(projectId) {
|
|
16
|
+
if (!projectId) return { root: canonicalTarget(resolveSkillsDir()) };
|
|
17
|
+
const project = new ProjectService().get(projectId);
|
|
18
|
+
if (!project?.workspaceRoot || !existsSync(project.workspaceRoot)) throw new ImportError("invalid_project", "Project needs an existing workspace");
|
|
19
|
+
return {
|
|
20
|
+
projectId,
|
|
21
|
+
root: canonicalTarget(resolveWorkspaceSkillsDir(project.workspaceRoot))
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function createRuntimeImportService(owner, refreshSkills, checkTrust) {
|
|
25
|
+
return new ImportService({
|
|
26
|
+
owner,
|
|
27
|
+
stateDir: resolveStateDir(),
|
|
28
|
+
refreshSkills,
|
|
29
|
+
isWorkspaceTrusted: (root) => checkTrust ? checkTrust(root) : new ProjectTrustStore().get(root) === true,
|
|
30
|
+
isConnected(candidate, sourceWorkspace) {
|
|
31
|
+
if (!candidate.shared) return false;
|
|
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;
|
|
34
|
+
return loadSkills({
|
|
35
|
+
workspaceDir,
|
|
36
|
+
workspaceTrust: workspaceDir && (checkTrust ? checkTrust(workspaceDir) : new ProjectTrustStore().get(workspaceDir) === true) ? "trusted" : "untrusted"
|
|
37
|
+
}).skills.some((s) => realpathSync(s.baseDir) === candidate.location);
|
|
38
|
+
},
|
|
39
|
+
validateTarget(target) {
|
|
40
|
+
if (resolveImportTarget(target.projectId).root !== target.root) throw new ImportError("scope_mismatch", "Project target changed; preview again", 409);
|
|
41
|
+
if (target.projectId) {
|
|
42
|
+
const project = new ProjectService().get(target.projectId);
|
|
43
|
+
if (!(checkTrust ? checkTrust(project.workspaceRoot) : new ProjectTrustStore().get(project.workspaceRoot) === true)) throw new ImportError("workspace_untrusted", "Trust this project in project settings before importing", 403);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
reservedNames(target) {
|
|
47
|
+
return loadSkills({
|
|
48
|
+
workspaceDir: target.projectId ? new ProjectService().get(target.projectId)?.workspaceRoot : void 0,
|
|
49
|
+
workspaceTrust: "trusted",
|
|
50
|
+
builtinDir: resolveBundledSkillsDir() ?? void 0
|
|
51
|
+
}).skills.map((s) => s.name);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async function recoverCapabilityImports() {
|
|
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);
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { createRuntimeImportService, recoverCapabilityImports, resolveImportTarget };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ImportScan, type ImportSource } from './types.js';
|
|
2
|
+
export { IMPORT_SOURCES } from './sources.js';
|
|
3
|
+
export interface ScanInput {
|
|
4
|
+
source: ImportSource;
|
|
5
|
+
root?: string;
|
|
6
|
+
projectRoot?: string;
|
|
7
|
+
home?: string;
|
|
8
|
+
projectOnly?: boolean;
|
|
9
|
+
budget?: {
|
|
10
|
+
bytes: number;
|
|
11
|
+
files: number;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export declare function scanLocal(input: ScanInput): Promise<ImportScan>;
|