@xopcai/xopc 0.0.395 → 0.0.397
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/src/plugin.js +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BRtkz-ZU.js → agent-avatar-dicebear-CIEGrioy.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-Dqg2NOn-.js → agent-defaults-settings-panel-CzKNVkRn.js} +1 -1
- package/dist/gateway/static/root/assets/agent-plugin-dialog-mpA9rwYd.js +1 -0
- package/dist/gateway/static/root/assets/{agents-0zgLsfVA.js → agents-7VMa7Rzo.js} +3 -3
- package/dist/gateway/static/root/assets/{app-management-settings-panel-BZS8FvoL.js → app-management-settings-panel-DIJ0ii-n.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-RxXMAxiB.js → appearance-settings-BGnyHS-p.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-CvqDUvof.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-DEnHq3OR.js → archive-plugin-CR8FcRAa.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-BwkugzB8.js → automations-page-BLInZo7N.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-workspace-K8aDhoYt.js → automations-workspace-DvTSHLt3.js} +1 -1
- package/dist/gateway/static/root/assets/{binary-plugins-NWeXhh1k.js → binary-plugins-DW9pu205.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-D_bKN3BN.js → block-editor-ClgTc5v8.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automation-inputs-vtPkXfJ8.js → browser-automation-inputs-Bn7kDuUB.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-DBPbwOG8.js → browser-automations-page-CJGBhDnv.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-settings-page-VBZDYoXD.js → browser-settings-page-tsi5W-cJ.js} +1 -1
- package/dist/gateway/static/root/assets/capabilities-page-CfbGk8q_.js +2 -0
- package/dist/gateway/static/root/assets/{capabilities-settings-panel-C_OMsW_8.js → capabilities-settings-panel-BkO7QEyA.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-header-actions-CMJXWq8a.js → capability-header-actions-9ZG71-BO.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-C_TVCc1N.js → channels-D0CwJktG.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-9YRAokOl.js → chat-terminal-dock-x1W2_hwc.js} +1 -1
- package/dist/gateway/static/root/assets/{computer-settings-page-BPYAZ4ks.js → computer-settings-page-D_b_bFVz.js} +1 -1
- package/dist/gateway/static/root/assets/{connector-service-page-B81cEAEX.js → connector-service-page-Kf8kaNm_.js} +1 -1
- package/dist/gateway/static/root/assets/{connectors-page-C0NvAP2V.js → connectors-page-DlpfL2Rc.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-BvKP4P3n.js → date-picker-d5kiPUG0.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-TvrNaf8y.js → dependency-picker-BcH76tQC.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-De-_QNSD.js → desktop-pet-B-925tPX.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-CRSRTIwT.js → desktop-pet-settings-3XJ4qR65.js} +1 -1
- package/dist/gateway/static/root/assets/device-pairing-wizard-12m5Z9-8.js +1 -0
- package/dist/gateway/static/root/assets/{directory-picker-path-field-DOPXwYNR.js → directory-picker-path-field-B-qZCjGR.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-DzXvpUwj.js → extension-debug-page-BlQu1u6X.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-C0S1zNeP.js → extension-page-CXxM1M62.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-C3Z7bFjy.js → extension-settings-page-AF5nvsPP.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-Btc2wTQe.js → gateway-settings-ABXvW_yL.js} +1 -1
- package/dist/gateway/static/root/assets/{highlighted-body-KQOG7T2V-BFYBbh1P.js → highlighted-body-KQOG7T2V-Czdbbi0H.js} +1 -1
- package/dist/gateway/static/root/assets/{home-page-C5sLR47Q.js → home-page-LXGxGwVl.js} +1 -1
- package/dist/gateway/static/root/assets/{image-generation-api-Vqcyqnzb.js → image-generation-api-DPFPJ2ps.js} +1 -1
- package/dist/gateway/static/root/assets/{imports-page-BJGHrzhX.js → imports-page-Bwyotfbt.js} +1 -1
- package/dist/gateway/static/root/assets/index-BKHxJaGD.css +1 -0
- package/dist/gateway/static/root/assets/{index-BNRLzxpH.js → index-Dsp_gNdl.js} +116 -116
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BQkQoPmm.js → keyboard-shortcuts-settings-C20gl-l7.js} +1 -1
- package/dist/gateway/static/root/assets/{local-app-workbench-page-DbxLz9Ms.js → local-app-workbench-page-o0M_OSjC.js} +1 -1
- package/dist/gateway/static/root/assets/{local-apps-page-CaatwSrK.js → local-apps-page-DoWJCmOq.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-3Wo4Lmts.js → logs-page-BFTL93RX.js} +1 -1
- package/dist/gateway/static/root/assets/{management-settings-BHlbM8IM.js → management-settings-C52cuMg1.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-Cq1uLrd8.js → media-plugins-DLrTRF0a.js} +1 -1
- package/dist/gateway/static/root/assets/mermaid-MCJ5UELQ-CPYJsRUJ.js +1 -0
- package/dist/gateway/static/root/assets/{note-detail-panel-MjKND8HC.js → note-detail-panel-CNN41byS.js} +3 -3
- package/dist/gateway/static/root/assets/{notes-page-Cv040hVD.js → notes-page-BsfBp_8z.js} +1 -1
- package/dist/gateway/static/root/assets/{page-context-capture-button-Cp71nTq2.js → page-context-capture-button-YTVWZ02-.js} +1 -1
- package/dist/gateway/static/root/assets/{personal-page-CyEUWV2n.js → personal-page-N4KoCZYG.js} +1 -1
- package/dist/gateway/static/root/assets/plugin-details-page-DcrrzQI-.js +1 -0
- package/dist/gateway/static/root/assets/{preview-open-alternatives-gzCecXc4.js → preview-open-alternatives-DzA__S54.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-CWFT7VuQ.js → product-open-page-DvlMKj0T.js} +1 -1
- package/dist/gateway/static/root/assets/{product-preview-content-Dh111Kca.js → product-preview-content-qdFyo4NG.js} +1 -1
- package/dist/gateway/static/root/assets/{project-detail-page-CyhyZiTi.js → project-detail-page-y67s_fA8.js} +1 -1
- package/dist/gateway/static/root/assets/{projects-page-QUdyJino.js → projects-page-D5iYgi0O.js} +1 -1
- package/dist/gateway/static/root/assets/{remote-access-hub-Cd8P0Dxs.js → remote-access-hub-DeFF7Re-.js} +1 -1
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-bTjVfYm5.js → runtime-tools-settings-panel-BCHLh-KQ.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-BGOzfVyj.js → schema-form-CJvaVHkx.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-BGMemJF6.js → sessions-page-DZ93_D5M.js} +1 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-F1WX7vxq.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-Bqm1IKvR.js → settings-form-section-B3eODxNv.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-CniJVL-o.js → settings-page-Dg86uCnr.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-JPs4-QRw.js → setup-status-panel-ruFzAArp.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-BnDGFgBR.js → share-preview-page-DCy1f25V.js} +1 -1
- package/dist/gateway/static/root/assets/{shares-settings-Cgp5BuLt.js → shares-settings-DeIVZvpJ.js} +1 -1
- package/dist/gateway/static/root/assets/{skill-api-CFdrz6tl.js → skill-api-RisyPAMA.js} +1 -1
- package/dist/gateway/static/root/assets/{skill-reload-api-Cy5jVfms.js → skill-reload-api-Dwkm1Fof.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-CxaA3P5E.js → skills-page-UiTLsrFc.js} +1 -1
- package/dist/gateway/static/root/assets/{style-C_MAnir7.js → style-CwMX73G7.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-BjnIlilP.js → system-settings-panel-Cma37Pfm.js} +1 -1
- package/dist/gateway/static/root/assets/{task-detail-page-Bg79rPnF.js → task-detail-page-BKOF_kxS.js} +1 -1
- package/dist/gateway/static/root/assets/{tracing-settings-page-BCI0tUbe.js → tracing-settings-page-DC-kLysq.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-0knnta9z.js → tunnel-api-BgdhbR1L.js} +1 -1
- package/dist/gateway/static/root/assets/{usage-page-D5aT1eie.js → usage-page-BSlFg3Xj.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-Ds_mbAfe.js → use-autosave-iKvZ1pvC.js} +1 -1
- package/dist/gateway/static/root/assets/{user-model-page-D7L0-pDt.js → user-model-page-D4GB8QsM.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-CPoGdarN.js → voice-api-key-field-CESbtuwM.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-CWNguy2Z.js → work-discovery-overlay-dtPpmYyh.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BcDMFfkT.js → workflow-run-setup-panel-DmFn3E_B.js} +1 -1
- package/dist/gateway/static/root/assets/{workflows-page-BwZB-s4N.js → workflows-page-DEtaRuxH.js} +1 -1
- package/dist/gateway/static/root/assets/{xopc-cloud-account-card-DlKr98VO.js → xopc-cloud-account-card-DTZnabfg.js} +1 -1
- package/dist/gateway/static/root/index.html +2 -2
- package/dist/package.js +1 -1
- package/dist/src/agent/embedded/codemode-extension.d.ts +1 -0
- package/dist/src/agent/embedded/codemode-extension.js +6 -1
- package/dist/src/agent/embedded/run-for-session.js +1 -1
- package/dist/src/agent/embedded/run-turn.js +34 -2
- package/dist/src/agent/embedded/session-runner.js +1 -1
- package/dist/src/agent/mcp/config.d.ts +2 -0
- package/dist/src/agent/mcp/config.js +2 -2
- package/dist/src/agent/mcp/connection-setup.js +13 -1
- package/dist/src/agent/mcp/effective-server.d.ts +3 -0
- package/dist/src/agent/mcp/effective-server.js +27 -0
- package/dist/src/agent/mcp/native-mcp.d.ts +1 -1
- package/dist/src/agent/mcp/native-mcp.js +13 -4
- package/dist/src/agent/mcp/tool-catalog.js +2 -1
- package/dist/src/agent/skills/index.d.ts +1 -0
- package/dist/src/agent/skills/index.js +6 -2
- package/dist/src/agent/skills/skill-manager.d.ts +1 -0
- package/dist/src/agent/skills/skill-manager.js +6 -1
- package/dist/src/agent/skills/skill-sources.d.ts +1 -0
- package/dist/src/agent/skills/skill-sources.js +4 -3
- package/dist/src/agent/skills/types.d.ts +1 -0
- package/dist/src/agent/workspace-runtime/registry.js +6 -2
- package/dist/src/browser/native-messaging-host.js +1 -1
- package/dist/src/browser/providers/browser-ext-install.js +2 -2
- package/dist/src/channels/index.js +1 -1
- package/dist/src/channels/manager.js +1 -1
- package/dist/src/cli/commands/channels.js +1 -1
- package/dist/src/cli/commands/connectors.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/extension-agent-plugin.js +92 -7
- package/dist/src/cli/commands/extension-marketplace.js +2 -2
- package/dist/src/cli/commands/extension.js +1 -1
- package/dist/src/cli/commands/mcp.js +6 -4
- package/dist/src/cli/commands/models.js +1 -1
- package/dist/src/cli/commands/onboard/model.js +1 -1
- package/dist/src/cli/commands/onboard.js +1 -1
- package/dist/src/extensions/agent-plugins/auth.d.ts +7 -1
- package/dist/src/extensions/agent-plugins/auth.js +53 -12
- package/dist/src/extensions/agent-plugins/branding.d.ts +6 -0
- package/dist/src/extensions/agent-plugins/branding.js +44 -0
- package/dist/src/extensions/agent-plugins/cli-authorization.d.ts +22 -0
- package/dist/src/extensions/agent-plugins/cli-authorization.js +198 -0
- package/dist/src/extensions/agent-plugins/connection-reuse.d.ts +10 -0
- package/dist/src/extensions/agent-plugins/connection-reuse.js +23 -0
- package/dist/src/extensions/agent-plugins/credentials.js +24 -1
- package/dist/src/extensions/agent-plugins/formats.d.ts +27 -0
- package/dist/src/extensions/agent-plugins/formats.js +208 -0
- package/dist/src/extensions/agent-plugins/inventory.d.ts +142 -2
- package/dist/src/extensions/agent-plugins/inventory.js +39 -5
- package/dist/src/extensions/agent-plugins/mcp-adapter.d.ts +2 -1
- package/dist/src/extensions/agent-plugins/mcp-adapter.js +42 -8
- package/dist/src/extensions/agent-plugins/mcp-format.d.ts +2 -0
- package/dist/src/extensions/agent-plugins/mcp-format.js +82 -0
- package/dist/src/extensions/agent-plugins/resources.d.ts +14 -0
- package/dist/src/extensions/agent-plugins/resources.js +112 -0
- package/dist/src/extensions/agent-plugins/setup-contract.d.ts +54 -0
- package/dist/src/extensions/agent-plugins/setup-contract.js +58 -0
- package/dist/src/extensions/agent-plugins/setup.d.ts +119 -0
- package/dist/src/extensions/agent-plugins/setup.js +491 -0
- package/dist/src/extensions/agent-plugins/source-catalog.d.ts +21 -0
- package/dist/src/extensions/agent-plugins/source-catalog.js +175 -0
- package/dist/src/extensions/agent-plugins/sources.d.ts +1 -1
- package/dist/src/extensions/agent-plugins/sources.js +12 -3
- package/dist/src/extensions/agent-plugins/store.d.ts +33 -2
- package/dist/src/extensions/agent-plugins/store.js +175 -25
- package/dist/src/extensions/agent-plugins/user-config.d.ts +5 -0
- package/dist/src/extensions/agent-plugins/user-config.js +49 -0
- package/dist/src/extensions/agent-plugins/validation.d.ts +26 -1
- package/dist/src/extensions/agent-plugins/validation.js +142 -27
- package/dist/src/extensions/sdk/index.js +1 -1
- package/dist/src/gateway/hono/app.js +1 -1
- package/dist/src/gateway/hono/routes/agent-plugins.js +120 -9
- package/dist/src/gateway/hono/routes/browser-install.js +1 -1
- package/dist/src/gateway/hono/routes/composer-history.js +22 -4
- package/dist/src/gateway/hono/routes/mcp.js +6 -4
- package/dist/src/gateway/hono/routes/provider-setup.js +7 -0
- package/dist/src/gateway/service.js +11 -9
- package/dist/src/gateway/session-context-summary.js +1 -1
- package/dist/src/gateway/xopc-cloud-capability-setup.d.ts +4 -0
- package/dist/src/gateway/xopc-cloud-capability-setup.js +90 -26
- package/dist/src/home-intelligence/host.js +2 -2
- package/dist/src/providers/setup/engine.js +12 -10
- package/dist/src/providers/setup/voice.js +24 -6
- package/dist/src/storage/sqlite/composer-input-history-repository.d.ts +4 -3
- package/dist/src/storage/sqlite/composer-input-history-repository.js +10 -8
- package/dist/src/storage/sqlite/device-pairing-approval.js +1 -1
- package/dist/src/storage/sqlite/migrations/257_session_composer_history.sql +11 -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/schema.js +1 -1
- package/dist/src/tui/backends/embedded-backend.d.ts +2 -2
- package/dist/src/tui/backends/embedded-backend.js +4 -4
- package/dist/src/tui/backends/gateway-realtime-backend.d.ts +2 -2
- package/dist/src/tui/backends/gateway-realtime-backend.js +7 -4
- package/dist/src/tui/composer-history.d.ts +14 -0
- package/dist/src/tui/composer-history.js +85 -0
- package/dist/src/tui/tui-backend.d.ts +4 -3
- package/dist/src/tui/tui.js +19 -46
- package/package.json +12 -4
- package/dist/gateway/static/root/assets/agent-plugin-dialog-CKK95Jmr.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-mGU9WLjP.js +0 -1
- package/dist/gateway/static/root/assets/capabilities-page-CQsGp9Sx.js +0 -2
- package/dist/gateway/static/root/assets/device-pairing-wizard-DnyJ8Bja.js +0 -1
- package/dist/gateway/static/root/assets/index-zIgp-ZTu.css +0 -1
- package/dist/gateway/static/root/assets/mermaid-MCJ5UELQ-XhlIBp5t.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-BiySFw1N.js +0 -1
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import { createLogger } from "../../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../../utils/logger.js";
|
|
3
|
+
import { init_write_file_atomic, writeTextAtomicSync } from "../../infra/write-file-atomic.js";
|
|
4
|
+
import { pluginAuthorizations } from "./setup-contract.js";
|
|
5
|
+
import { preparePluginResources } from "./resources.js";
|
|
6
|
+
import { pluginName, pluginServerId } from "./validation.js";
|
|
7
|
+
import { AgentPluginStore } from "./store.js";
|
|
8
|
+
import { connectionFingerprint, matchingReusableConnection } from "./connection-reuse.js";
|
|
9
|
+
import { createNativeMcpCredentials } from "../../agent/mcp/credential-store.js";
|
|
10
|
+
import { readPluginAuthBinding, savePluginAuthBinding, savePluginOAuthReference } from "./auth.js";
|
|
11
|
+
import { pluginConfigMissing, readPluginUserConfig, savePluginUserConfig } from "./user-config.js";
|
|
12
|
+
import { materializePluginServers } from "./mcp-adapter.js";
|
|
13
|
+
import { getNativePiAgentDir } from "../../agent/mcp/native-mcp.js";
|
|
14
|
+
import { cancelMcpAuthorization, getMcpAuthorization, probeMcpConnection, startMcpAuthorization, submitMcpRedirect } from "../../agent/mcp/connection-setup.js";
|
|
15
|
+
import { cancelPluginCliAuthorizations, checkPluginCliAuthorization, getPluginCliAuthorization, startPluginCliAuthorization } from "./cli-authorization.js";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
+
import { randomUUID } from "node:crypto";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import lockfile from "proper-lockfile";
|
|
21
|
+
//#region src/extensions/agent-plugins/setup.ts
|
|
22
|
+
init_logger();
|
|
23
|
+
init_write_file_atomic();
|
|
24
|
+
const connection = z.object({
|
|
25
|
+
name: z.string(),
|
|
26
|
+
state: z.enum([
|
|
27
|
+
"pending",
|
|
28
|
+
"connected",
|
|
29
|
+
"oauth_required",
|
|
30
|
+
"token_required",
|
|
31
|
+
"error",
|
|
32
|
+
"skipped"
|
|
33
|
+
]),
|
|
34
|
+
toolCount: z.number().optional(),
|
|
35
|
+
authorizationId: z.string().optional()
|
|
36
|
+
});
|
|
37
|
+
const schema = z.object({
|
|
38
|
+
id: z.string().uuid(),
|
|
39
|
+
pluginId: pluginName,
|
|
40
|
+
owner: z.string(),
|
|
41
|
+
packageRevision: z.string().uuid(),
|
|
42
|
+
revision: z.number().int(),
|
|
43
|
+
state: z.enum([
|
|
44
|
+
"pending",
|
|
45
|
+
"running",
|
|
46
|
+
"action_required",
|
|
47
|
+
"completed",
|
|
48
|
+
"cancelled",
|
|
49
|
+
"interrupted",
|
|
50
|
+
"failed"
|
|
51
|
+
]),
|
|
52
|
+
resources: z.enum([
|
|
53
|
+
"pending",
|
|
54
|
+
"running",
|
|
55
|
+
"ready",
|
|
56
|
+
"error"
|
|
57
|
+
]),
|
|
58
|
+
connections: z.array(connection),
|
|
59
|
+
installed: z.boolean().default(false),
|
|
60
|
+
deferred: z.boolean().default(false),
|
|
61
|
+
authorizations: z.array(z.object({
|
|
62
|
+
id: z.string(),
|
|
63
|
+
state: z.enum([
|
|
64
|
+
"pending",
|
|
65
|
+
"connected",
|
|
66
|
+
"skipped",
|
|
67
|
+
"error"
|
|
68
|
+
]),
|
|
69
|
+
method: z.string().optional(),
|
|
70
|
+
attemptId: z.string().optional()
|
|
71
|
+
})).default([]),
|
|
72
|
+
operations: z.array(z.object({
|
|
73
|
+
key: z.string().uuid(),
|
|
74
|
+
action: z.string()
|
|
75
|
+
})).max(100).default([]),
|
|
76
|
+
missingConfig: z.array(z.string()).default([]),
|
|
77
|
+
partial: z.boolean(),
|
|
78
|
+
updatedAt: z.string(),
|
|
79
|
+
error: z.string().optional()
|
|
80
|
+
});
|
|
81
|
+
const actionRevision = {
|
|
82
|
+
expectedRevision: z.number().int(),
|
|
83
|
+
idempotencyKey: z.string().uuid().optional()
|
|
84
|
+
};
|
|
85
|
+
const setupActionSchema = z.discriminatedUnion("action", [
|
|
86
|
+
z.strictObject({
|
|
87
|
+
action: z.literal("prepare"),
|
|
88
|
+
...actionRevision
|
|
89
|
+
}),
|
|
90
|
+
z.strictObject({
|
|
91
|
+
action: z.literal("reuse"),
|
|
92
|
+
...actionRevision,
|
|
93
|
+
server: z.string(),
|
|
94
|
+
sourceServerId: z.string()
|
|
95
|
+
}),
|
|
96
|
+
z.strictObject({
|
|
97
|
+
action: z.literal("authorize"),
|
|
98
|
+
...actionRevision,
|
|
99
|
+
server: z.string(),
|
|
100
|
+
method: z.string().optional()
|
|
101
|
+
}),
|
|
102
|
+
z.strictObject({
|
|
103
|
+
action: z.literal("configure"),
|
|
104
|
+
...actionRevision,
|
|
105
|
+
values: z.record(z.string(), z.unknown()),
|
|
106
|
+
authorization: z.string().optional(),
|
|
107
|
+
method: z.string().optional()
|
|
108
|
+
}),
|
|
109
|
+
z.strictObject({
|
|
110
|
+
action: z.literal("complete"),
|
|
111
|
+
...actionRevision,
|
|
112
|
+
workspacePaths: z.array(z.string().min(1)).max(100).nullable().optional(),
|
|
113
|
+
allowPartial: z.boolean().default(false)
|
|
114
|
+
}),
|
|
115
|
+
z.strictObject({
|
|
116
|
+
action: z.literal("defer"),
|
|
117
|
+
...actionRevision
|
|
118
|
+
}),
|
|
119
|
+
z.strictObject({
|
|
120
|
+
action: z.literal("cancel"),
|
|
121
|
+
...actionRevision
|
|
122
|
+
})
|
|
123
|
+
]);
|
|
124
|
+
const log = createLogger("PluginSetup");
|
|
125
|
+
const running = /* @__PURE__ */ new Map();
|
|
126
|
+
var PluginSetupService = class {
|
|
127
|
+
constructor(store = new AgentPluginStore()) {
|
|
128
|
+
this.store = store;
|
|
129
|
+
}
|
|
130
|
+
path(id) {
|
|
131
|
+
return join(this.store.stateDir, "plugin-setup", `${pluginName.parse(id)}.json`);
|
|
132
|
+
}
|
|
133
|
+
key(id) {
|
|
134
|
+
return this.path(id);
|
|
135
|
+
}
|
|
136
|
+
write(job) {
|
|
137
|
+
job.revision++;
|
|
138
|
+
job.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
139
|
+
writeTextAtomicSync(this.path(job.pluginId), JSON.stringify(schema.parse(job)));
|
|
140
|
+
}
|
|
141
|
+
get(id, owner) {
|
|
142
|
+
if (!existsSync(this.path(id))) return;
|
|
143
|
+
const job = schema.parse(JSON.parse(readFileSync(this.path(id), "utf8")));
|
|
144
|
+
if (job.owner !== owner) throw new Error("Setup belongs to another principal");
|
|
145
|
+
if (job.state === "running" && !running.has(this.key(id)) && !lockfile.checkSync(this.path(id), { realpath: false })) {
|
|
146
|
+
job.state = "interrupted";
|
|
147
|
+
job.error = "Preparation was interrupted. Retry to continue.";
|
|
148
|
+
this.write(job);
|
|
149
|
+
}
|
|
150
|
+
return job;
|
|
151
|
+
}
|
|
152
|
+
create(id, owner) {
|
|
153
|
+
const plugin = this.store.pending(id) ?? this.store.get(id);
|
|
154
|
+
if (!plugin || plugin.readiness === "blocked") throw new Error("Plugin unavailable");
|
|
155
|
+
const previous = this.get(id, owner);
|
|
156
|
+
if (previous?.packageRevision === plugin.receipt.revision && previous.state !== "cancelled") return previous;
|
|
157
|
+
if (running.has(this.key(id)) || lockfile.checkSync(this.path(id), { realpath: false })) throw new Error("Preparation is already running");
|
|
158
|
+
const job = {
|
|
159
|
+
id: randomUUID(),
|
|
160
|
+
pluginId: id,
|
|
161
|
+
owner,
|
|
162
|
+
packageRevision: plugin.receipt.revision,
|
|
163
|
+
revision: 0,
|
|
164
|
+
state: "pending",
|
|
165
|
+
resources: "pending",
|
|
166
|
+
connections: Object.keys(plugin.servers).map((name) => ({
|
|
167
|
+
name,
|
|
168
|
+
state: "pending"
|
|
169
|
+
})),
|
|
170
|
+
installed: false,
|
|
171
|
+
deferred: false,
|
|
172
|
+
authorizations: pluginAuthorizations(plugin.manifest).map((value) => ({
|
|
173
|
+
id: value.id,
|
|
174
|
+
state: "pending"
|
|
175
|
+
})),
|
|
176
|
+
operations: [],
|
|
177
|
+
missingConfig: pluginConfigMissing(this.store, plugin),
|
|
178
|
+
partial: plugin.diagnostics.length > 0,
|
|
179
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
180
|
+
};
|
|
181
|
+
this.write(job);
|
|
182
|
+
return job;
|
|
183
|
+
}
|
|
184
|
+
invalidateConnection(id, owner, name) {
|
|
185
|
+
const job = this.get(id, owner);
|
|
186
|
+
if (!job) return;
|
|
187
|
+
running.get(this.key(id))?.abort();
|
|
188
|
+
const server = job.connections.find((value) => value.name === name);
|
|
189
|
+
if (server) {
|
|
190
|
+
if (server.authorizationId) cancelMcpAuthorization(server.authorizationId, owner);
|
|
191
|
+
server.state = "pending";
|
|
192
|
+
delete server.authorizationId;
|
|
193
|
+
}
|
|
194
|
+
job.state = "action_required";
|
|
195
|
+
this.write(job);
|
|
196
|
+
}
|
|
197
|
+
async reusableConnections(id, owner, config) {
|
|
198
|
+
if (!this.get(id, owner)) return {};
|
|
199
|
+
const plugin = this.store.pending(id) ?? this.store.get(id);
|
|
200
|
+
if (!plugin) return {};
|
|
201
|
+
getNativePiAgentDir();
|
|
202
|
+
const credentials = await createNativeMcpCredentials();
|
|
203
|
+
const result = {};
|
|
204
|
+
for (const [name, target] of Object.entries(plugin.servers)) {
|
|
205
|
+
result[name] = [];
|
|
206
|
+
for (const [sourceId, source] of Object.entries(config.mcp?.servers ?? {})) {
|
|
207
|
+
if (!matchingReusableConnection(target, source) || !source.url || !source.xopcOAuthCredentialId) continue;
|
|
208
|
+
if (await credentials.tokens(source.xopcOAuthCredentialId, source.url)) result[name].push({
|
|
209
|
+
id: sourceId,
|
|
210
|
+
name: source.displayName ?? sourceId
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
async authorization(id, owner, server, config) {
|
|
217
|
+
const job = this.get(id, owner);
|
|
218
|
+
if (!job) throw new Error("Setup not found");
|
|
219
|
+
const declared = pluginAuthorizations((this.store.pending(id) ?? this.store.get(id)).manifest).find((value) => value.id === server);
|
|
220
|
+
const selected = job.authorizations.find((value) => value.id === server);
|
|
221
|
+
const method = declared?.methods.find((value) => value.id === selected?.method);
|
|
222
|
+
if (method?.type === "cli") return selected?.attemptId ? getPluginCliAuthorization(selected.attemptId, owner, job.packageRevision) : void 0;
|
|
223
|
+
const target = job.connections.find((c) => c.name === (method?.type === "oauth" ? method.server : server));
|
|
224
|
+
if (!target?.authorizationId) return void 0;
|
|
225
|
+
const effective = this.config(id, job.packageRevision, config);
|
|
226
|
+
return getMcpAuthorization(target.authorizationId, owner, effective);
|
|
227
|
+
}
|
|
228
|
+
redirect(id, owner, server, url, config) {
|
|
229
|
+
const job = this.get(id, owner);
|
|
230
|
+
const declaration = pluginAuthorizations((this.store.pending(id) ?? this.store.get(id)).manifest).find((value) => value.id === server);
|
|
231
|
+
const selected = job?.authorizations.find((value) => value.id === server);
|
|
232
|
+
const method = declaration?.methods.find((value) => value.id === selected?.method);
|
|
233
|
+
const attempt = job?.connections.find((c) => c.name === (method?.type === "oauth" ? method.server : server))?.authorizationId;
|
|
234
|
+
return !!job && !!attempt && submitMcpRedirect(attempt, owner, url, this.config(id, job.packageRevision, config));
|
|
235
|
+
}
|
|
236
|
+
config(id, revision, config) {
|
|
237
|
+
const plugin = this.store.pending(id) ?? this.store.get(id);
|
|
238
|
+
if (!plugin || plugin.receipt.revision !== revision || plugin.readiness === "blocked") throw new Error("Plugin revision changed; restart setup");
|
|
239
|
+
const servers = materializePluginServers(plugin, this.store, [], true);
|
|
240
|
+
return {
|
|
241
|
+
...config,
|
|
242
|
+
mcp: {
|
|
243
|
+
...config.mcp,
|
|
244
|
+
servers: {
|
|
245
|
+
...config.mcp?.servers,
|
|
246
|
+
...servers
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
startPrepare(id, owner, revision, config, workspace, idempotencyKey) {
|
|
252
|
+
const current = this.get(id, owner);
|
|
253
|
+
if (idempotencyKey && current?.operations.some((op) => op.key === idempotencyKey && op.action === "prepare")) return current;
|
|
254
|
+
if (!current || current.revision !== revision) throw new Error("Setup changed. Refresh and retry.");
|
|
255
|
+
if (current.state === "running") return current;
|
|
256
|
+
if (lockfile.checkSync(this.path(id), { realpath: false })) throw new Error("Preparation is already running");
|
|
257
|
+
this.act(id, owner, {
|
|
258
|
+
action: "prepare",
|
|
259
|
+
expectedRevision: revision,
|
|
260
|
+
idempotencyKey
|
|
261
|
+
}, config, workspace).catch(() => {
|
|
262
|
+
log.warn({ pluginId: id }, "Plugin preparation could not continue");
|
|
263
|
+
});
|
|
264
|
+
return this.get(id, owner);
|
|
265
|
+
}
|
|
266
|
+
async act(id, owner, input, config, workspace) {
|
|
267
|
+
const current = this.get(id, owner);
|
|
268
|
+
const previous = input.idempotencyKey && current?.operations.find((op) => op.key === input.idempotencyKey);
|
|
269
|
+
if (previous) {
|
|
270
|
+
if (previous.action !== input.action) throw new Error("Idempotency key belongs to another action");
|
|
271
|
+
return current;
|
|
272
|
+
}
|
|
273
|
+
const remember = (job) => {
|
|
274
|
+
if (input.idempotencyKey && (job.state !== "cancelled" || input.action === "cancel")) {
|
|
275
|
+
job.operations = [...job.operations, {
|
|
276
|
+
key: input.idempotencyKey,
|
|
277
|
+
action: input.action
|
|
278
|
+
}].slice(-100);
|
|
279
|
+
this.write(job);
|
|
280
|
+
}
|
|
281
|
+
return job;
|
|
282
|
+
};
|
|
283
|
+
if (input.action === "cancel") return remember(await this.perform(id, owner, input, config, workspace));
|
|
284
|
+
const release = lockfile.lockSync(this.path(id), { realpath: false });
|
|
285
|
+
try {
|
|
286
|
+
return remember(await this.perform(id, owner, input, config, workspace));
|
|
287
|
+
} finally {
|
|
288
|
+
release();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async perform(id, owner, input, config, workspace) {
|
|
292
|
+
const job = this.get(id, owner);
|
|
293
|
+
if (!job) throw new Error("Setup not found");
|
|
294
|
+
if (job.revision !== input.expectedRevision) throw new Error("Setup changed. Refresh and retry.");
|
|
295
|
+
if (input.action === "cancel") {
|
|
296
|
+
running.get(this.key(id))?.abort();
|
|
297
|
+
cancelPluginCliAuthorizations(id, owner);
|
|
298
|
+
for (const server of job.connections) if (server.authorizationId) cancelMcpAuthorization(server.authorizationId, owner);
|
|
299
|
+
job.state = "cancelled";
|
|
300
|
+
this.write(job);
|
|
301
|
+
this.store.discardPending(id);
|
|
302
|
+
return job;
|
|
303
|
+
}
|
|
304
|
+
if (running.has(this.key(id))) throw new Error("Preparation is already running");
|
|
305
|
+
if (job.state === "cancelled") throw new Error("Restart setup before continuing");
|
|
306
|
+
const effective = this.config(id, job.packageRevision, config);
|
|
307
|
+
const plugin = this.store.pending(id) ?? this.store.get(id);
|
|
308
|
+
const declarations = pluginAuthorizations(plugin.manifest);
|
|
309
|
+
if (input.action === "configure") {
|
|
310
|
+
const controller = new AbortController();
|
|
311
|
+
running.set(this.key(id), controller);
|
|
312
|
+
try {
|
|
313
|
+
if (input.authorization || input.method) {
|
|
314
|
+
const declaration = declarations.find((value) => value.id === input.authorization);
|
|
315
|
+
const method = declaration?.methods.find((value) => value.id === input.method);
|
|
316
|
+
if (!method || !("fields" in method) || Object.keys(input.values).some((key) => !method.fields.includes(key))) throw new Error("Invalid credential form");
|
|
317
|
+
const saved = readPluginUserConfig(this.store, id, plugin.receipt.revision);
|
|
318
|
+
if (method.fields.some((key) => input.values[key] === void 0 && saved[key] === void 0 || typeof input.values[key] === "string" && !input.values[key].trim())) throw new Error("Complete the required credential fields");
|
|
319
|
+
const target = job.authorizations.find((value) => value.id === declaration.id);
|
|
320
|
+
target.method = method.id;
|
|
321
|
+
target.state = "pending";
|
|
322
|
+
delete target.attemptId;
|
|
323
|
+
}
|
|
324
|
+
cancelPluginCliAuthorizations(id, owner);
|
|
325
|
+
await savePluginUserConfig(this.store, plugin, input.values);
|
|
326
|
+
controller.signal.throwIfAborted();
|
|
327
|
+
this.config(id, job.packageRevision, config);
|
|
328
|
+
for (const server of job.connections) {
|
|
329
|
+
if (input.authorization && !declarations.find((value) => value.id === input.authorization)?.servers.includes(server.name)) continue;
|
|
330
|
+
if (server.authorizationId) cancelMcpAuthorization(server.authorizationId, owner);
|
|
331
|
+
server.state = "pending";
|
|
332
|
+
delete server.authorizationId;
|
|
333
|
+
}
|
|
334
|
+
job.missingConfig = pluginConfigMissing(this.store, plugin);
|
|
335
|
+
job.state = "action_required";
|
|
336
|
+
this.write(job);
|
|
337
|
+
return job;
|
|
338
|
+
} finally {
|
|
339
|
+
running.delete(this.key(id));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (input.action === "reuse") {
|
|
343
|
+
const source = config.mcp?.servers?.[input.sourceServerId];
|
|
344
|
+
const candidates = await this.reusableConnections(id, owner, config);
|
|
345
|
+
if (!source?.xopcOAuthCredentialId || !candidates[input.server]?.some((value) => value.id === input.sourceServerId)) throw new Error("This connection is unavailable or has different endpoint or OAuth scopes");
|
|
346
|
+
if (this.get(id, owner)?.revision !== job.revision) throw new Error("Setup changed. Refresh and retry.");
|
|
347
|
+
savePluginOAuthReference(this.store, id, input.server, {
|
|
348
|
+
sourceServerId: input.sourceServerId,
|
|
349
|
+
credentialId: source.xopcOAuthCredentialId,
|
|
350
|
+
fingerprint: connectionFingerprint(source)
|
|
351
|
+
});
|
|
352
|
+
const server = job.connections.find((value) => value.name === input.server);
|
|
353
|
+
if (server.authorizationId) cancelMcpAuthorization(server.authorizationId, owner);
|
|
354
|
+
server.state = "pending";
|
|
355
|
+
delete server.authorizationId;
|
|
356
|
+
job.state = "action_required";
|
|
357
|
+
this.write(job);
|
|
358
|
+
return job;
|
|
359
|
+
}
|
|
360
|
+
if (input.action === "authorize") {
|
|
361
|
+
const declaration = declarations.find((value) => value.id === input.server);
|
|
362
|
+
const method = declaration?.methods.find((value) => value.id === input.method);
|
|
363
|
+
if (declaration && !method) throw new Error("Choose an authorization method");
|
|
364
|
+
if (method?.type === "form") throw new Error("Complete this method using its credential form");
|
|
365
|
+
if (declaration && method) {
|
|
366
|
+
if (job.resources !== "ready") throw new Error("Prepare resources before signing in");
|
|
367
|
+
const target = job.authorizations.find((value) => value.id === declaration.id);
|
|
368
|
+
target.method = method.id;
|
|
369
|
+
target.state = "pending";
|
|
370
|
+
for (const server of job.connections) if (declaration.servers.includes(server.name)) {
|
|
371
|
+
if (server.authorizationId) cancelMcpAuthorization(server.authorizationId, owner);
|
|
372
|
+
server.state = "pending";
|
|
373
|
+
delete server.authorizationId;
|
|
374
|
+
}
|
|
375
|
+
if (method.type === "cli") {
|
|
376
|
+
target.attemptId = startPluginCliAuthorization(this.store, plugin, declaration.id, method, owner).id;
|
|
377
|
+
job.state = "action_required";
|
|
378
|
+
this.write(job);
|
|
379
|
+
return job;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const server = job.connections.find((c) => c.name === (method?.type === "oauth" ? method.server : input.server));
|
|
383
|
+
if (!server) throw new Error("Unknown setup connection");
|
|
384
|
+
const target = plugin.servers[server.name];
|
|
385
|
+
if (target && readPluginAuthBinding(this.store, id, server.name, target, plugin.receipt.revision)?.oauthReference) await savePluginAuthBinding(this.store, id, server.name, { mode: "auto" });
|
|
386
|
+
if (this.get(id, owner)?.revision !== job.revision) throw new Error("Setup changed. Refresh and retry.");
|
|
387
|
+
server.authorizationId = startMcpAuthorization(this.config(id, job.packageRevision, config), workspace, pluginServerId(id, server.name), owner, { authorizationScope: `${job.id}:${server.name}` }).id;
|
|
388
|
+
job.state = "action_required";
|
|
389
|
+
this.write(job);
|
|
390
|
+
return job;
|
|
391
|
+
}
|
|
392
|
+
if (input.action === "complete" || input.action === "defer") {
|
|
393
|
+
const deferred = input.action === "defer";
|
|
394
|
+
if (job.resources !== "ready" || !deferred && pluginConfigMissing(this.store, plugin).length) throw new Error("Prepare resources and configuration first");
|
|
395
|
+
const partial = plugin.diagnostics.length > 0 || job.connections.some((c) => c.state !== "connected") || job.authorizations.some((value) => value.state !== "connected") || pluginConfigMissing(this.store, plugin).length > 0;
|
|
396
|
+
if (partial && !deferred && !("allowPartial" in input && input.allowPartial)) throw new Error("Some capabilities are unavailable. Review before enabling partially.");
|
|
397
|
+
if (plugin.layout?.compatibility.some((item) => item.component === "hooks")) throw new Error("Lifecycle hooks are not supported; keep this plugin installed and disabled until a compatible adapter is available");
|
|
398
|
+
this.store.commitPending(id);
|
|
399
|
+
this.store.setWorkspaceScope(id, "workspacePaths" in input ? input.workspacePaths : void 0);
|
|
400
|
+
this.store.setEnabled(id, true);
|
|
401
|
+
if (deferred) {
|
|
402
|
+
delete job.error;
|
|
403
|
+
cancelPluginCliAuthorizations(id, owner);
|
|
404
|
+
for (const server of job.connections) if (server.authorizationId && server.state !== "connected") cancelMcpAuthorization(server.authorizationId, owner);
|
|
405
|
+
}
|
|
406
|
+
job.installed = true;
|
|
407
|
+
job.deferred = deferred && partial;
|
|
408
|
+
job.partial = partial;
|
|
409
|
+
job.state = "completed";
|
|
410
|
+
this.write(job);
|
|
411
|
+
return job;
|
|
412
|
+
}
|
|
413
|
+
const controller = new AbortController();
|
|
414
|
+
running.set(this.key(id), controller);
|
|
415
|
+
const cancellationPoll = setInterval(() => {
|
|
416
|
+
try {
|
|
417
|
+
const current = this.get(id, owner);
|
|
418
|
+
if (!current || current.id !== job.id || current.revision !== job.revision || current.state === "cancelled" || !this.store.receipt(id)) controller.abort();
|
|
419
|
+
} catch {
|
|
420
|
+
controller.abort();
|
|
421
|
+
}
|
|
422
|
+
}, 500);
|
|
423
|
+
cancellationPoll.unref();
|
|
424
|
+
job.state = "running";
|
|
425
|
+
job.resources = "running";
|
|
426
|
+
delete job.error;
|
|
427
|
+
this.write(job);
|
|
428
|
+
try {
|
|
429
|
+
await preparePluginResources(this.store, plugin, controller.signal);
|
|
430
|
+
job.resources = "ready";
|
|
431
|
+
job.missingConfig = pluginConfigMissing(this.store, plugin);
|
|
432
|
+
if (job.missingConfig.length) {
|
|
433
|
+
job.state = "action_required";
|
|
434
|
+
job.error = "Complete the required configuration fields.";
|
|
435
|
+
} else {
|
|
436
|
+
for (const target of job.authorizations) {
|
|
437
|
+
const method = declarations.find((value) => value.id === target.id).methods.find((value) => value.id === target.method);
|
|
438
|
+
if (method?.type === "cli") target.state = await checkPluginCliAuthorization(this.store, plugin, target.id, method, controller.signal) ? "connected" : "pending";
|
|
439
|
+
else if (method?.type === "form") {
|
|
440
|
+
const values = readPluginUserConfig(this.store, id, plugin.receipt.revision);
|
|
441
|
+
target.state = method.fields.every((key) => values[key] !== void 0 && values[key] !== "") ? "connected" : "pending";
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
for (const server of job.connections) {
|
|
445
|
+
controller.signal.throwIfAborted();
|
|
446
|
+
if (declarations.filter((value) => value.servers.includes(server.name)).some((gate) => {
|
|
447
|
+
const target = job.authorizations.find((value) => value.id === gate.id);
|
|
448
|
+
const method = gate.methods.find((value) => value.id === target.method);
|
|
449
|
+
return !method || method.type !== "oauth" && target.state !== "connected";
|
|
450
|
+
})) {
|
|
451
|
+
server.state = "skipped";
|
|
452
|
+
delete server.toolCount;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
const result = await probeMcpConnection(effective, workspace, pluginServerId(id, server.name), { signal: controller.signal });
|
|
456
|
+
controller.signal.throwIfAborted();
|
|
457
|
+
server.state = result.state;
|
|
458
|
+
server.toolCount = result.toolCount;
|
|
459
|
+
const persisted = this.get(id, owner);
|
|
460
|
+
if (!persisted || persisted.id !== job.id || persisted.revision !== job.revision || persisted.state === "cancelled" || !this.store.receipt(id)) throw new Error("Setup changed");
|
|
461
|
+
this.config(id, job.packageRevision, config);
|
|
462
|
+
this.write(job);
|
|
463
|
+
}
|
|
464
|
+
for (const target of job.authorizations) target.state = declarations.find((value) => value.id === target.id).servers.every((name) => job.connections.find((value) => value.name === name)?.state === "connected") ? "connected" : "pending";
|
|
465
|
+
job.partial = plugin.diagnostics.length > 0;
|
|
466
|
+
if (job.connections.some((value) => value.state === "error")) job.error = "Connection verification failed. Check the credentials or login status and try again.";
|
|
467
|
+
job.state = "action_required";
|
|
468
|
+
}
|
|
469
|
+
this.config(id, job.packageRevision, config);
|
|
470
|
+
controller.signal.throwIfAborted();
|
|
471
|
+
if (this.get(id, owner)?.id !== job.id || this.get(id, owner)?.state === "cancelled") throw new Error("Setup changed");
|
|
472
|
+
this.write(job);
|
|
473
|
+
} catch {
|
|
474
|
+
const current = this.get(id, owner);
|
|
475
|
+
if (!controller.signal.aborted && current?.id === job.id && current.revision === job.revision && current.state !== "cancelled" && (this.store.pending(id) ?? this.store.get(id))?.receipt.revision === job.packageRevision) {
|
|
476
|
+
job.state = "failed";
|
|
477
|
+
if (job.resources !== "ready") job.resources = "error";
|
|
478
|
+
job.error = "Preparation failed. Retry after checking resources and connections.";
|
|
479
|
+
this.write(job);
|
|
480
|
+
}
|
|
481
|
+
} finally {
|
|
482
|
+
clearInterval(cancellationPoll);
|
|
483
|
+
running.delete(this.key(id));
|
|
484
|
+
}
|
|
485
|
+
const latest = this.get(id, owner);
|
|
486
|
+
if (!latest) throw new Error("Setup was removed");
|
|
487
|
+
return latest;
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
//#endregion
|
|
491
|
+
export { PluginSetupService, setupActionSchema };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface PluginCandidate {
|
|
2
|
+
name: string;
|
|
3
|
+
source: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class PluginSourceRedirect extends Error {
|
|
7
|
+
readonly source: string;
|
|
8
|
+
constructor(source: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class PluginSelectionRequired extends Error {
|
|
11
|
+
readonly candidates: PluginCandidate[];
|
|
12
|
+
constructor(candidates: PluginCandidate[]);
|
|
13
|
+
}
|
|
14
|
+
export declare const isGitPluginSource: (source: string) => boolean;
|
|
15
|
+
/** Fetch and validate a fixed tree without running hooks, submodules or package scripts. */
|
|
16
|
+
export declare function withCatalogSource<T>(source: string, fn: (local: string, metadata: {
|
|
17
|
+
origin: string;
|
|
18
|
+
commit?: string;
|
|
19
|
+
importManifest?: Record<string, unknown>;
|
|
20
|
+
}) => T | Promise<T>): Promise<T>;
|
|
21
|
+
export declare const pluginSourceKey: (source: string) => string;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { PLUGIN_MANIFEST_PATHS } from "./formats.js";
|
|
2
|
+
import { containedPath } from "./validation.js";
|
|
3
|
+
import { runExec } from "../../infra/exec.js";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
//#region src/extensions/agent-plugins/source-catalog.ts
|
|
9
|
+
var PluginSourceRedirect = class extends Error {
|
|
10
|
+
constructor(source) {
|
|
11
|
+
super("Resolve marketplace plugin source");
|
|
12
|
+
this.source = source;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var PluginSelectionRequired = class extends Error {
|
|
16
|
+
constructor(candidates) {
|
|
17
|
+
super("Select a plugin from this marketplace.");
|
|
18
|
+
this.candidates = candidates;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const isGitPluginSource = (source) => /^(git\+https:\/\/|git\+ssh:\/\/|github:)/.test(source) || /^https:\/\/github\.com\/[^/]+\/[^/#]+(?:\.git)?(?:#.*)?$/.test(source);
|
|
22
|
+
/** Fetch and validate a fixed tree without running hooks, submodules or package scripts. */
|
|
23
|
+
async function withCatalogSource(source, fn) {
|
|
24
|
+
const [origin, fragment = ""] = source.split("#", 2);
|
|
25
|
+
const options = new URLSearchParams(fragment);
|
|
26
|
+
const plugin = options.get("plugin");
|
|
27
|
+
const subdir = options.get("path") ?? ".";
|
|
28
|
+
const temp = isGitPluginSource(source) ? mkdtempSync(join(tmpdir(), "xopc-plugin-git-")) : void 0;
|
|
29
|
+
try {
|
|
30
|
+
let root = origin;
|
|
31
|
+
let commit;
|
|
32
|
+
if (temp) {
|
|
33
|
+
let url = origin.replace(/^git\+/, "").replace(/^github:([^/]+\/[^/]+)$/, "https://github.com/$1.git");
|
|
34
|
+
const parsed = new URL(url);
|
|
35
|
+
if (!["https:", "ssh:"].includes(parsed.protocol) || parsed.password || parsed.search || parsed.protocol === "https:" && parsed.username) throw new Error("Use an HTTPS or SSH Git repository without embedded credentials");
|
|
36
|
+
if (parsed.protocol === "ssh:" && (!/^[A-Za-z0-9][A-Za-z0-9.-]*$/.test(parsed.hostname) || parsed.username && !/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/.test(parsed.username))) throw new Error("Invalid SSH repository host or user");
|
|
37
|
+
url = parsed.href;
|
|
38
|
+
const ref = options.get("ref") ?? "HEAD";
|
|
39
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_./-]{0,199}$/.test(ref) || ref.includes("..")) throw new Error("Invalid Git reference");
|
|
40
|
+
const env = {
|
|
41
|
+
...Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_"))),
|
|
42
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
43
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
44
|
+
GIT_CONFIG_GLOBAL: "/dev/null"
|
|
45
|
+
};
|
|
46
|
+
const run = (args) => runExec("git", [
|
|
47
|
+
"-c",
|
|
48
|
+
"core.hooksPath=/dev/null",
|
|
49
|
+
"-c",
|
|
50
|
+
"protocol.file.allow=never",
|
|
51
|
+
...args
|
|
52
|
+
], {
|
|
53
|
+
cwd: temp,
|
|
54
|
+
env,
|
|
55
|
+
timeoutMs: 3e4,
|
|
56
|
+
maxBuffer: 1024 * 1024
|
|
57
|
+
});
|
|
58
|
+
await run(["init", "--quiet"]);
|
|
59
|
+
await run([
|
|
60
|
+
"fetch",
|
|
61
|
+
"--depth=1",
|
|
62
|
+
"--no-tags",
|
|
63
|
+
"--",
|
|
64
|
+
url,
|
|
65
|
+
ref
|
|
66
|
+
]);
|
|
67
|
+
commit = (await run([
|
|
68
|
+
"rev-parse",
|
|
69
|
+
"--verify",
|
|
70
|
+
"FETCH_HEAD^{commit}"
|
|
71
|
+
])).stdout.trim();
|
|
72
|
+
const inventory = (await run([
|
|
73
|
+
"ls-tree",
|
|
74
|
+
"-r",
|
|
75
|
+
"-l",
|
|
76
|
+
commit
|
|
77
|
+
])).stdout.trim().split("\n").filter(Boolean);
|
|
78
|
+
let expandedBytes = 0;
|
|
79
|
+
if (inventory.length > 1e4) throw new Error("Git package exceeds file limit");
|
|
80
|
+
for (const line of inventory) {
|
|
81
|
+
const item = /^(100644|100755) blob [a-f0-9]+\s+(\d+)\t/.exec(line);
|
|
82
|
+
if (!item) throw new Error("Git symlinks and submodules are not supported");
|
|
83
|
+
expandedBytes += Number(item[2]);
|
|
84
|
+
if (expandedBytes > 100 * 1024 * 1024) throw new Error("Git package exceeds 100 MiB");
|
|
85
|
+
}
|
|
86
|
+
await run([
|
|
87
|
+
"-c",
|
|
88
|
+
"core.symlinks=false",
|
|
89
|
+
"checkout",
|
|
90
|
+
"--force",
|
|
91
|
+
"--detach",
|
|
92
|
+
commit
|
|
93
|
+
]);
|
|
94
|
+
rmSync(join(temp, ".git"), {
|
|
95
|
+
recursive: true,
|
|
96
|
+
force: true
|
|
97
|
+
});
|
|
98
|
+
root = temp;
|
|
99
|
+
}
|
|
100
|
+
root = containedPath(root, join(root, subdir));
|
|
101
|
+
let importManifest;
|
|
102
|
+
const catalog = options.get("package") === "1" ? void 0 : [".agents/plugins/marketplace.json", ".claude-plugin/marketplace.json"].map((p) => join(root, p)).find(existsSync);
|
|
103
|
+
if (catalog) {
|
|
104
|
+
if (statSync(catalog).size > 1024 * 1024) throw new Error("Marketplace exceeds 1 MiB");
|
|
105
|
+
const manifest = JSON.parse(readFileSync(containedPath(root, catalog), "utf8"));
|
|
106
|
+
if (!Array.isArray(manifest.plugins) || manifest.plugins.length > 1e3) throw new Error("Invalid marketplace plugin list");
|
|
107
|
+
const entries = manifest.plugins;
|
|
108
|
+
if (!plugin) throw new PluginSelectionRequired(entries.map((entry) => ({
|
|
109
|
+
name: String(entry.name),
|
|
110
|
+
description: typeof entry.description === "string" ? entry.description : void 0,
|
|
111
|
+
source: `${origin}#${new URLSearchParams({
|
|
112
|
+
...Object.fromEntries(options),
|
|
113
|
+
plugin: String(entry.name)
|
|
114
|
+
})}`
|
|
115
|
+
})));
|
|
116
|
+
const matches = entries.filter((entry) => entry.name === plugin);
|
|
117
|
+
if (matches.length !== 1) throw new Error("Marketplace plugin is missing or ambiguous");
|
|
118
|
+
const entry = matches[0];
|
|
119
|
+
const refSource = entry.source;
|
|
120
|
+
let rel = typeof refSource === "string" ? refSource : void 0;
|
|
121
|
+
if (refSource && typeof refSource === "object") {
|
|
122
|
+
const local = refSource;
|
|
123
|
+
if (local.source === "local") rel = local.path;
|
|
124
|
+
if (local.source === "url" && typeof local.url === "string" && local.url.startsWith("./")) rel = local.url;
|
|
125
|
+
}
|
|
126
|
+
if (typeof refSource === "object" && refSource) {
|
|
127
|
+
const remote = refSource;
|
|
128
|
+
if (rel === void 0 && (remote.source === "github" || remote.source === "url" || remote.source === "git-subdir")) {
|
|
129
|
+
const target = remote.source === "github" ? `github:${remote.repo}` : `git+${remote.url}`;
|
|
130
|
+
const query = new URLSearchParams({ package: "1" });
|
|
131
|
+
if (typeof remote.sha === "string" || typeof remote.ref === "string") query.set("ref", String(remote.sha ?? remote.ref));
|
|
132
|
+
if (remote.source === "git-subdir" && typeof remote.path === "string") query.set("path", remote.path);
|
|
133
|
+
throw new PluginSourceRedirect(`${target}#${query}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (typeof rel !== "string" || !rel.startsWith("./") && rel !== ".") throw new Error("This marketplace source type is not supported; install its repository or archive directly");
|
|
137
|
+
root = containedPath(root, join(root, rel));
|
|
138
|
+
const hasManifest = PLUGIN_MANIFEST_PATHS.some((path) => existsSync(join(root, path)));
|
|
139
|
+
if (hasManifest && [
|
|
140
|
+
"skills",
|
|
141
|
+
"mcpServers",
|
|
142
|
+
"hooks",
|
|
143
|
+
"commands",
|
|
144
|
+
"agents"
|
|
145
|
+
].some((key) => entry[key] !== void 0)) throw new Error("Marketplace component overlays are not supported by this compatibility version");
|
|
146
|
+
if (!hasManifest && catalog.endsWith(".claude-plugin/marketplace.json")) importManifest = Object.fromEntries(Object.entries(entry).filter(([key]) => ![
|
|
147
|
+
"source",
|
|
148
|
+
"strict",
|
|
149
|
+
"category",
|
|
150
|
+
"tags"
|
|
151
|
+
].includes(key)));
|
|
152
|
+
} else if (plugin) throw new Error("No marketplace found at this source");
|
|
153
|
+
return await fn(root, {
|
|
154
|
+
origin: source,
|
|
155
|
+
commit,
|
|
156
|
+
importManifest
|
|
157
|
+
});
|
|
158
|
+
} finally {
|
|
159
|
+
if (temp) rmSync(temp, {
|
|
160
|
+
recursive: true,
|
|
161
|
+
force: true
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const pluginSourceKey = (source) => {
|
|
166
|
+
if (source.startsWith("store:")) return createHash("sha256").update(source.replace(/@[^/]+$/, "")).digest("hex").slice(0, 12);
|
|
167
|
+
const [origin, fragment = ""] = source.split("#", 2);
|
|
168
|
+
const params = new URLSearchParams(fragment);
|
|
169
|
+
params.delete("ref");
|
|
170
|
+
params.sort();
|
|
171
|
+
const canonical = (/^(?:github:|git\+|https:\/\/)/.test(origin) ? origin : existsSync(origin) ? realpathSync(origin) : resolve(origin)).replace(/^git\+/, "").replace(/^github:([^/]+\/[^/]+)$/, "https://github.com/$1").replace(/\.git$/, "").replace(/\/$/, "");
|
|
172
|
+
return createHash("sha256").update(`${canonical}#${params}`).digest("hex").slice(0, 12);
|
|
173
|
+
};
|
|
174
|
+
//#endregion
|
|
175
|
+
export { PluginSelectionRequired, PluginSourceRedirect, isGitPluginSource, pluginSourceKey, withCatalogSource };
|