openpond 0.0.38 → 0.0.39
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/README.md +3 -4
- package/dist/api/core.d.ts +6 -0
- package/dist/api/vercel-protection.d.ts +1 -0
- package/dist/chunks/{app-layer-DPUJRNO2.js → app-layer-LTBWNA2F.js} +32 -10
- package/dist/chunks/app-server-runtime-VYYJ2AWX.js +18 -0
- package/dist/chunks/{apps-AKBI6AS7.js → apps-CZAJBGE3.js} +6 -4
- package/dist/chunks/{chunk-LZZM3IHS.js → chunk-2SF6HLT7.js} +5899 -5939
- package/dist/chunks/{chunk-XCATKTK3.js → chunk-3CDPPVLE.js} +108 -66
- package/dist/chunks/{chunk-JWF37O5E.js → chunk-3HW6OSHP.js} +1 -3
- package/dist/chunks/{chunk-43YI36NA.js → chunk-3KLQ52B2.js} +228 -5
- package/dist/chunks/chunk-4HIWFIMN.js +37172 -0
- package/dist/chunks/{chunk-AHPQTEIJ.js → chunk-4N6E3OVF.js} +4 -4
- package/dist/chunks/{chunk-YTJL6XPU.js → chunk-6V7MJVRO.js} +56 -11
- package/dist/chunks/{chunk-QRM7XMAO.js → chunk-BQQZNGI6.js} +152 -161
- package/dist/chunks/chunk-DBHSOTYZ.js +1552 -0
- package/dist/chunks/{chunk-275WCETC.js → chunk-FCVVOMIP.js} +145 -5
- package/dist/chunks/{chunk-GB6VUZUA.js → chunk-JIEUB6OS.js} +1 -1
- package/dist/chunks/{chunk-HS2AGRDV.js → chunk-JJPAMKXZ.js} +1 -1
- package/dist/chunks/{chunk-GDZYDLEL.js → chunk-JRROTGQT.js} +1 -110
- package/dist/chunks/chunk-KHNHUA2H.js +1737 -0
- package/dist/chunks/{chunk-AHNFNWUN.js → chunk-L6XH4ZGZ.js} +1 -1
- package/dist/chunks/chunk-M2NXTLH2.js +4054 -0
- package/dist/chunks/{chunk-SLMP353Q.js → chunk-MIZHCABO.js} +2 -195
- package/dist/chunks/chunk-SMNFI2FU.js +113 -0
- package/dist/chunks/chunk-VCVQLECH.js +675 -0
- package/dist/chunks/{chunk-V35STJF4.js → chunk-ZJBJQCQZ.js} +179 -52
- package/dist/chunks/{cli-7BKDWFNU.js → cli-JS76LCW2.js} +9 -5
- package/dist/chunks/{core-commands-YN4RMWA3.js → core-commands-4MRJQZJV.js} +7 -5
- package/dist/chunks/{harness-BHQRMXJR.js → desktop-test-RNMWLOVU.js} +15 -14
- package/dist/chunks/{chunk-XERQHYBO.js → extension-GDRW22N3.js} +5868 -3580
- package/dist/chunks/{help-VREQNA4I.js → help-LWNREJ7U.js} +1 -1
- package/dist/chunks/{opchat-D63YTNSX.js → opchat-2K2JIGK3.js} +9 -10
- package/dist/chunks/{organizations-I46KG7TC.js → organizations-Z5HVD2E7.js} +11 -9
- package/dist/chunks/{profile-WKZJHEPG.js → profile-QNECVVGN.js} +12 -38
- package/dist/chunks/{project-agent-ZUC3YMCT.js → project-agent-W3V75WBR.js} +9 -411
- package/dist/chunks/{sandbox-command-AAOPFTI5.js → sandbox-command-X3FO74NX.js} +7 -5
- package/dist/chunks/{sandbox-template-GKNVR4C5.js → sandbox-template-5B4T4BGU.js} +8 -6
- package/dist/chunks/{src-RPW5TOPI.js → src-ESAILPUH.js} +32 -30
- package/dist/chunks/src-IC5RX4L6.js +63517 -0
- package/dist/chunks/{teams-bot-PSL3ZO6Z.js → teams-bot-NTQ6VFFL.js} +6 -4
- package/dist/chunks/training-BR5A7SPU.js +320 -0
- package/dist/chunks/{workspaces-BPXC2DHT.js → workspaces-XC3QBD2Y.js} +2 -2
- package/dist/cli.js +24 -12
- package/dist/config.d.ts +9 -3
- package/dist/index.js +1300 -1045
- package/dist/profile/local-profile-types.d.ts +16 -0
- package/dist/profile/local-profile.d.ts +50 -4
- package/dist/sandbox/client-handles.d.ts +33 -46
- package/dist/sandbox/client.d.ts +4 -26
- package/dist/sandbox/sandbox-instance-client.d.ts +5 -5
- package/dist/sandbox/types/client.d.ts +1 -2
- package/dist/sandbox/types/core.d.ts +0 -1
- package/dist/sandbox/types/org-project-agent.d.ts +3 -89
- package/dist/sandbox/types/record.d.ts +1 -2
- package/dist/sandbox/types/runtime-profiles.d.ts +2 -2
- package/dist/sandbox-template/manifest.d.ts +1 -1
- package/dist/skills/openpond-agent-authoring/SKILL.md +40 -0
- package/dist/skills/openpond-agent-authoring/references/action-and-chat-design.md +13 -0
- package/dist/skills/openpond-agent-authoring/references/integrations-and-setup.md +13 -0
- package/dist/skills/openpond-agent-authoring/references/profile-layout.md +29 -0
- package/dist/skills/openpond-agent-authoring/references/validation-and-repair.md +14 -0
- package/dist/skills/openpond-desktop-harness/SKILL.md +1 -1
- package/dist/skills/openpond-skill-authoring/SKILL.md +44 -0
- package/dist/skills/openpond-skill-authoring/references/copying-and-adaptation.md +16 -0
- package/dist/skills/openpond-skill-authoring/references/skill-package-layout.md +38 -0
- package/dist/skills/openpond-skill-authoring/references/validation-and-repair.md +24 -0
- package/dist/skills/openpond-skill-authoring/scripts/validate-skill.mjs +102 -0
- package/dist/skills/openpond-taskset-authoring/SKILL.md +64 -22
- package/dist/skills/openpond-taskset-authoring/artifact.json +39 -0
- package/dist/skills/openpond-taskset-authoring/references/graders-and-rewards.md +3 -0
- package/dist/skills/openpond-taskset-authoring/references/method-selection.md +1 -1
- package/dist/skills/openpond-taskset-authoring/references/privacy-and-provenance.md +2 -0
- package/dist/skills/openpond-taskset-authoring/references/task-design.md +32 -0
- package/dist/web/assets/{AppDialog-DrwJb_wG.js → AppDialog-Bua3f-6v.js} +1 -1
- package/dist/web/assets/{AppsView-DPagLMsc.js → AppsView-DrJkHeSO.js} +1 -1
- package/dist/web/assets/BrowserSidebar-Bf-4Vozm.js +1 -0
- package/dist/web/assets/CommandMenu-DDZlGQiB.js +1 -0
- package/dist/web/assets/CommunityView-BtcCrba7.js +1 -0
- package/dist/web/assets/ComposerCreateImproveStrip-DOe5Otg6.js +1 -0
- package/dist/web/assets/GetStartedView-CG4z0Dtw.js +1 -0
- package/dist/web/assets/GetStartedView-yVmnGZPw.css +1 -0
- package/dist/web/assets/LabModelVersionDetailPage-BEkmSKuv.js +1 -0
- package/dist/web/assets/LabSkillSidebar-BIotN_RB.js +1 -0
- package/dist/web/assets/LabsRoute-CfOe0Zt5.js +3 -0
- package/dist/web/assets/LabsRoute-CoSPO4HR.css +1 -0
- package/dist/web/assets/MainChatThread-C0-l1mHt.js +2 -0
- package/dist/web/assets/MainPane-Bky_Rqcx.css +1 -0
- package/dist/web/assets/MainPane-aOVlVyRK.js +6 -0
- package/dist/web/assets/MarkdownText-D7DT1k1k.js +9 -0
- package/dist/web/assets/Messages-CBIXruA9.js +9 -0
- package/dist/web/assets/NativeSkillSidebar-C4t69Y7B.js +1 -0
- package/dist/web/assets/NewProjectDialog-Bat65tYD.js +1 -0
- package/dist/web/assets/OutputsPage-C58n39Db.js +1 -0
- package/dist/web/assets/RightChatPanelStack-Bp7nZxQ0.js +1 -0
- package/dist/web/assets/ScheduledWorkPage-BXP0j_c_.js +1 -0
- package/dist/web/assets/SettingsView-CtWK_aQ0.css +1 -0
- package/dist/web/assets/SettingsView-DOOfhO9a.js +6 -0
- package/dist/web/assets/TeamChatView-Cgxm-pKA.css +1 -0
- package/dist/web/assets/TeamChatView-De2wdqyY.js +3 -0
- package/dist/web/assets/{TerminalOverlay-BKl0p8pa.js → TerminalOverlay-BsuCH3NX.js} +1 -1
- package/dist/web/assets/TrainingCreationPanel-D2OFWyQ-.js +1 -0
- package/dist/web/assets/TrainingDraftPanel-DBpulXTz.js +1 -0
- package/dist/web/assets/{UsageSettingsSection-zDAwzTGl.js → UsageSettingsSection-DzMLUXAO.js} +37 -37
- package/dist/web/assets/WorkspaceDiffPanel-B7yIhjNF.js +14 -0
- package/dist/web/assets/WorkspaceEnvironmentMenu-B6jXM9xG.js +32 -0
- package/dist/web/assets/WorkspaceGitDialogs-CQ0fl9Qk.js +1 -0
- package/dist/web/assets/{WorkspaceMonacoEditor-DPGcG8fy.js → WorkspaceMonacoEditor-C2ctzITz.js} +3 -3
- package/dist/web/assets/arrow-up-right-ecwcx3xt.js +1 -0
- package/dist/web/assets/chevron-up-DP8PPfWd.js +1 -0
- package/dist/web/assets/circle-alert-envg1D6O.js +1 -0
- package/dist/web/assets/cloud-upload-5MGPLnaA.js +1 -0
- package/dist/web/assets/{cssMode-CKN9CcfG.js → cssMode-CFifcEfw.js} +1 -1
- package/dist/web/assets/folder-open-Dq3ZjeEP.js +1 -0
- package/dist/web/assets/folder-plus-EiZ5wbv_.js +1 -0
- package/dist/web/assets/git-branch-w29jmQ-X.js +1 -0
- package/dist/web/assets/{git-commit-horizontal-Bxi8YPep.js → git-commit-horizontal-CGZM0PZo.js} +1 -1
- package/dist/web/assets/{htmlMode-CeUcNhBZ.js → htmlMode-Ct1P-_lf.js} +1 -1
- package/dist/web/assets/index-ByIQyiUl.js +178 -0
- package/dist/web/assets/index-D4uCeiep.js +1 -0
- package/dist/web/assets/index-D9jTe16Q.css +1 -0
- package/dist/web/assets/info-Cd2HkIeb.js +1 -0
- package/dist/web/assets/{jsonMode-97z0Yz9u.js → jsonMode-BAUNCNAX.js} +1 -1
- package/dist/web/assets/{lspLanguageFeatures-D4Tp-JmI.js → lspLanguageFeatures-fucFYI1A.js} +1 -1
- package/dist/web/assets/monaco.contribution-Ce4v5kei.js +2 -0
- package/dist/web/assets/monaco.contribution-DLLMi4cB.js +2 -0
- package/dist/web/assets/monaco.contribution-g9QiIm1d.js +2 -0
- package/dist/web/assets/monaco.contribution-nG-5Cyjz.js +2 -0
- package/dist/web/assets/monitor-DgHCnCKl.js +1 -0
- package/dist/web/assets/{play-C-37dnR1.js → play-Be0XDHBu.js} +1 -1
- package/dist/web/assets/{python-CGpDXY7_.js → python-CEhEiPo7.js} +1 -1
- package/dist/web/assets/{refresh-cw-BAaaPCXl.js → refresh-cw-DH0KLbdD.js} +1 -1
- package/dist/web/assets/reply-D1TK2yV8.js +1 -0
- package/dist/web/assets/{save-Cvxs3XyC.js → save-C1MSeBnQ.js} +1 -1
- package/dist/web/assets/square-CemI-0g5.js +1 -0
- package/dist/web/assets/square-pen-CqfsvR_n.js +1 -0
- package/dist/web/assets/{toggleHighContrast-CD42jH4g.js → toggleHighContrast-DrdQN3Rl.js} +1 -1
- package/dist/web/assets/training-BQVNUTZA.css +1 -0
- package/dist/web/assets/training-model-data-B7-1Bxil.js +1 -0
- package/dist/web/assets/{tsMode-ZROIxEjv.js → tsMode-CKB5oOUU.js} +1 -1
- package/dist/web/assets/{upload-DvCSGERK.js → upload-t751LLqP.js} +1 -1
- package/dist/web/assets/useLocalAgentSchedules-CzC3wfIP.js +1 -0
- package/dist/web/assets/{wifi-off-BIb94PkS.js → wifi-off-CxTHH3JU.js} +1 -1
- package/dist/web/assets/{workers-oYqbrVYG.js → workers-CqYddzCd.js} +1 -1
- package/dist/web/assets/{yaml-WqBjNgOO.js → yaml-e5wBkc-C.js} +1 -1
- package/dist/web/index.html +2 -2
- package/docs/command-reference.md +75 -40
- package/package.json +3 -1
- package/dist/chunks/chunk-FWLOFUPU.js +0 -864
- package/dist/chunks/chunk-HLCTINEP.js +0 -224
- package/dist/chunks/chunk-MPGSHXGF.js +0 -3896
- package/dist/chunks/chunk-T4QXKU5Y.js +0 -1
- package/dist/chunks/chunk-XEJ7XXUQ.js +0 -1301
- package/dist/chunks/chunk-ZI4GDAUB.js +0 -3522
- package/dist/chunks/cli-2KBVNDRY.js +0 -23
- package/dist/chunks/extend-REGNTBK3.js +0 -89
- package/dist/chunks/extension-7K77DAVL.js +0 -121
- package/dist/chunks/fileFromPath-DHCRY3BD.js +0 -2151
- package/dist/chunks/src-MWDIK56B.js +0 -105881
- package/dist/web/assets/BrowserSidebar-DMn2V8RV.js +0 -1
- package/dist/web/assets/CloudWorkView-Cl_7mM3c.js +0 -1
- package/dist/web/assets/CloudWorkView-DNoPIlMj.css +0 -1
- package/dist/web/assets/CommandMenu-CHMQLez_.js +0 -1
- package/dist/web/assets/CommunityView-DcHVDUk0.js +0 -1
- package/dist/web/assets/ComposerCreateImproveStrip-BZ3Dgzmo.js +0 -1
- package/dist/web/assets/GetStartedView-DBLdduwr.css +0 -1
- package/dist/web/assets/GetStartedView-DTRotFqz.js +0 -1
- package/dist/web/assets/LabSkillSidebar-D-PqFe1T.js +0 -1
- package/dist/web/assets/LabsRoute-BrJkBNig.css +0 -1
- package/dist/web/assets/LabsRoute-Dew1BwK5.js +0 -3
- package/dist/web/assets/MainChatThread-BaPxxzrw.js +0 -2
- package/dist/web/assets/MakeAgentTutorialLearningPanel-CBsfnONQ.js +0 -2
- package/dist/web/assets/MarkdownText-DGLfAM_Z.js +0 -7
- package/dist/web/assets/Messages-CIX9uTjX.js +0 -10
- package/dist/web/assets/NativeSkillSidebar-AP_uHn6g.js +0 -1
- package/dist/web/assets/NewProjectDialog-C6lHkafU.js +0 -1
- package/dist/web/assets/PostTrainingLearningPanel-CZEgPc8O.js +0 -2
- package/dist/web/assets/PostTrainingStatusPill-CeEpwUDB.js +0 -1
- package/dist/web/assets/ProfileSettingsSection-Cg4tKO-C.css +0 -1
- package/dist/web/assets/ProfileSettingsSection-EumMLlEp.js +0 -1
- package/dist/web/assets/RightChatPanelStack-MXBcx3zO.js +0 -1
- package/dist/web/assets/SettingsView-Caljbns2.css +0 -1
- package/dist/web/assets/SettingsView-DyURG_Uh.js +0 -6
- package/dist/web/assets/TeamChatView-Bx44q3WV.css +0 -1
- package/dist/web/assets/TeamChatView-D3v6aqJ3.js +0 -3
- package/dist/web/assets/TrainingCreationPanel-CfZQTuT5.js +0 -1
- package/dist/web/assets/TrainingDraftPanel-DslRPSDW.js +0 -1
- package/dist/web/assets/WorkspaceDiffPanel-C_MZPPlL.js +0 -14
- package/dist/web/assets/WorkspaceEnvironmentMenu-BUAnn80j.js +0 -32
- package/dist/web/assets/WorkspaceGitDialogs-Drbf4ujA.js +0 -1
- package/dist/web/assets/activity-Dnr3jqb3.js +0 -1
- package/dist/web/assets/boxes-BinC8UPn.js +0 -1
- package/dist/web/assets/chevron-up-Bjp_-_Cb.js +0 -1
- package/dist/web/assets/circle-x-ubnvEYuE.js +0 -1
- package/dist/web/assets/copy-B_ZCCUMz.js +0 -1
- package/dist/web/assets/create-pipeline-request-D6uLUxe9.js +0 -4
- package/dist/web/assets/file-8HAMlTmQ.js +0 -1
- package/dist/web/assets/git-branch-l2Zih3Bv.js +0 -1
- package/dist/web/assets/index-BMFyOFRU.css +0 -1
- package/dist/web/assets/index-BzwcDzbT.js +0 -1
- package/dist/web/assets/index-KoQSSt_f.js +0 -177
- package/dist/web/assets/info-JTbDQxHx.js +0 -1
- package/dist/web/assets/make-agent-tutorial-D6nBCgv-.js +0 -2
- package/dist/web/assets/monaco.contribution-B3j6oown.js +0 -2
- package/dist/web/assets/monaco.contribution-BXHxPPWL.js +0 -2
- package/dist/web/assets/monaco.contribution-C2P5zIzh.js +0 -2
- package/dist/web/assets/monaco.contribution-C8c6OtGi.js +0 -2
- package/dist/web/assets/monitor-kzHtxHnA.js +0 -1
- package/dist/web/assets/reply-9rehU13k.js +0 -1
- package/dist/web/assets/rotate-cw-C1Z_Xs23.js +0 -1
- package/dist/web/assets/training-BN8CALIc.css +0 -1
- package/dist/web/assets/training-model-data-BXUCzS0p.js +0 -1
- package/docs/agent-edit-check-status-json.md +0 -132
|
@@ -1,864 +0,0 @@
|
|
|
1
|
-
import { createRequire as __openpondCreateRequire } from "node:module"; var require = __openpondCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
parseProfileSkillMarkdown
|
|
4
|
-
} from "./chunk-V35STJF4.js";
|
|
5
|
-
import {
|
|
6
|
-
openPondConfigDirectory,
|
|
7
|
-
updatePrivateJsonFile
|
|
8
|
-
} from "./chunk-ZI4GDAUB.js";
|
|
9
|
-
|
|
10
|
-
// ../../packages/cloud/src/extensions/extension-manager.ts
|
|
11
|
-
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
12
|
-
import { existsSync } from "node:fs";
|
|
13
|
-
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
14
|
-
import path2 from "node:path";
|
|
15
|
-
|
|
16
|
-
// ../../packages/cloud/src/extensions/github-source.ts
|
|
17
|
-
import { createHash } from "node:crypto";
|
|
18
|
-
import path from "node:path";
|
|
19
|
-
|
|
20
|
-
// ../../packages/cloud/src/extensions/types.ts
|
|
21
|
-
var GithubExtensionError = class extends Error {
|
|
22
|
-
code;
|
|
23
|
-
status;
|
|
24
|
-
constructor(message, options) {
|
|
25
|
-
super(message);
|
|
26
|
-
this.name = "GithubExtensionError";
|
|
27
|
-
this.code = options.code;
|
|
28
|
-
this.status = options.status ?? 400;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// ../../packages/cloud/src/extensions/github-source.ts
|
|
33
|
-
var DEFAULT_REF = "HEAD";
|
|
34
|
-
var MAX_EXTENSION_SKILLS = 64;
|
|
35
|
-
var MAX_EXTENSION_FILES = 512;
|
|
36
|
-
var MAX_EXTENSION_BYTES = 20 * 1024 * 1024;
|
|
37
|
-
var MAX_EXTENSION_FILE_BYTES = 5 * 1024 * 1024;
|
|
38
|
-
var DOWNLOAD_CONCURRENCY = 8;
|
|
39
|
-
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
40
|
-
var GITHUB_REPOSITORY_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
41
|
-
var ROOT_SKILL_RESOURCE_DIRECTORIES = /* @__PURE__ */ new Set(["agents", "assets", "references", "scripts"]);
|
|
42
|
-
function createGithubSourceClient(options = {}) {
|
|
43
|
-
const fetchImpl = options.fetch ?? fetch;
|
|
44
|
-
const githubToken = options.githubToken?.trim() || process.env.GITHUB_TOKEN?.trim() || process.env.GH_TOKEN?.trim() || null;
|
|
45
|
-
return {
|
|
46
|
-
async resolve(request) {
|
|
47
|
-
const source = await resolveSource(request, fetchImpl, githubToken);
|
|
48
|
-
const preview = await buildPreview(source, fetchImpl, githubToken);
|
|
49
|
-
return {
|
|
50
|
-
preview,
|
|
51
|
-
files: filesToDownload(source)
|
|
52
|
-
};
|
|
53
|
-
},
|
|
54
|
-
async download(resolution, writeFile2) {
|
|
55
|
-
await downloadFiles({
|
|
56
|
-
resolution,
|
|
57
|
-
fetchImpl,
|
|
58
|
-
githubToken,
|
|
59
|
-
writeFile: writeFile2
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
function parseGithubExtensionSource(source) {
|
|
65
|
-
const trimmed = source.trim();
|
|
66
|
-
let owner = "";
|
|
67
|
-
let repo = "";
|
|
68
|
-
if (/^(?:https?:\/\/)?github\.com\//i.test(trimmed)) {
|
|
69
|
-
const url = new URL(trimmed.startsWith("http") ? trimmed : `https://${trimmed}`);
|
|
70
|
-
if (url.hostname.toLowerCase() !== "github.com") {
|
|
71
|
-
throw invalidSource(source);
|
|
72
|
-
}
|
|
73
|
-
const segments = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
|
|
74
|
-
if (segments.length !== 2) throw invalidSource(source);
|
|
75
|
-
[owner = "", repo = ""] = segments;
|
|
76
|
-
} else {
|
|
77
|
-
const normalized = trimmed.replace(/^github:/i, "").replace(/^\/+|\/+$/g, "");
|
|
78
|
-
const segments = normalized.split("/");
|
|
79
|
-
if (segments.length !== 2) throw invalidSource(source);
|
|
80
|
-
[owner = "", repo = ""] = segments;
|
|
81
|
-
}
|
|
82
|
-
repo = repo.replace(/\.git$/i, "");
|
|
83
|
-
if (!GITHUB_REPOSITORY_PATTERN.test(owner) || !GITHUB_REPOSITORY_PATTERN.test(repo)) {
|
|
84
|
-
throw invalidSource(source);
|
|
85
|
-
}
|
|
86
|
-
const normalizedOwner = owner.toLowerCase();
|
|
87
|
-
const normalizedRepo = repo.toLowerCase();
|
|
88
|
-
return {
|
|
89
|
-
id: `github:${normalizedOwner}/${normalizedRepo}`,
|
|
90
|
-
owner: normalizedOwner,
|
|
91
|
-
repo: normalizedRepo,
|
|
92
|
-
repositoryUrl: `https://github.com/${normalizedOwner}/${normalizedRepo}`
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
async function resolveSource(request, fetchImpl, githubToken) {
|
|
96
|
-
const identity = parseGithubExtensionSource(request.source);
|
|
97
|
-
const requestedRef = request.ref?.trim() || DEFAULT_REF;
|
|
98
|
-
const repository = await githubJson(fetchImpl, githubToken, `/repos/${identity.owner}/${identity.repo}`);
|
|
99
|
-
const resolvedRef = requestedRef === DEFAULT_REF ? repository.default_branch?.trim() || "main" : requestedRef;
|
|
100
|
-
const commit = await githubJson(
|
|
101
|
-
fetchImpl,
|
|
102
|
-
githubToken,
|
|
103
|
-
`/repos/${identity.owner}/${identity.repo}/commits/${encodeURIComponent(resolvedRef)}`
|
|
104
|
-
);
|
|
105
|
-
const resolvedCommit = commit.sha?.trim();
|
|
106
|
-
const treeSha = commit.commit?.tree?.sha?.trim();
|
|
107
|
-
if (!resolvedCommit || !treeSha) {
|
|
108
|
-
throw new GithubExtensionError("GitHub returned an incomplete commit response.", {
|
|
109
|
-
code: "github_invalid_commit",
|
|
110
|
-
status: 502
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
const treePayload = await githubJson(
|
|
114
|
-
fetchImpl,
|
|
115
|
-
githubToken,
|
|
116
|
-
`/repos/${identity.owner}/${identity.repo}/git/trees/${treeSha}?recursive=1`
|
|
117
|
-
);
|
|
118
|
-
if (treePayload.truncated) {
|
|
119
|
-
throw new GithubExtensionError("The GitHub repository tree is too large to install safely.", {
|
|
120
|
-
code: "github_tree_truncated"
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
const tree = (treePayload.tree ?? []).filter(validTreeEntry);
|
|
124
|
-
const skillPackages = discoverSkillPackages(tree);
|
|
125
|
-
if (skillPackages.length === 0) {
|
|
126
|
-
throw new GithubExtensionError(
|
|
127
|
-
"No skills found. Add SKILL.md at the repository root or use a standard agent skills directory.",
|
|
128
|
-
{ code: "extension_no_skills" }
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
if (skillPackages.length > MAX_EXTENSION_SKILLS) {
|
|
132
|
-
throw new GithubExtensionError(`Extension contains more than ${MAX_EXTENSION_SKILLS} skills.`, {
|
|
133
|
-
code: "extension_too_many_skills"
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
validateDownloadLimits(skillPackages);
|
|
137
|
-
const readme = tree.find((entry) => entry.type === "blob" && /^readme(?:\.[^/]+)?$/i.test(entry.path)) ?? null;
|
|
138
|
-
return { identity, requestedRef, resolvedCommit, tree, skillPackages, readme };
|
|
139
|
-
}
|
|
140
|
-
async function buildPreview(source, fetchImpl, githubToken) {
|
|
141
|
-
const skills = await mapConcurrent(source.skillPackages, DOWNLOAD_CONCURRENCY, async (skillPackage) => {
|
|
142
|
-
const contents = await downloadText(source, skillPackage.entry, fetchImpl, githubToken);
|
|
143
|
-
return previewSkill(skillPackage, contents);
|
|
144
|
-
});
|
|
145
|
-
const duplicateNames = duplicateValues(skills.map((skill) => skill.name));
|
|
146
|
-
for (const skill of skills) {
|
|
147
|
-
if (!duplicateNames.has(skill.name)) continue;
|
|
148
|
-
skill.validationStatus = "error";
|
|
149
|
-
skill.validationMessages = [...skill.validationMessages, `Duplicate skill name: ${skill.name}`];
|
|
150
|
-
}
|
|
151
|
-
const validationMessages = skills.flatMap(
|
|
152
|
-
(skill) => skill.validationMessages.map((message) => `${skill.relativePath}: ${message}`)
|
|
153
|
-
);
|
|
154
|
-
const packageFiles = filesToDownload(source);
|
|
155
|
-
return {
|
|
156
|
-
...source.identity,
|
|
157
|
-
source: "github",
|
|
158
|
-
requestedRef: source.requestedRef,
|
|
159
|
-
resolvedCommit: source.resolvedCommit,
|
|
160
|
-
sourcePath: null,
|
|
161
|
-
readmePath: source.readme?.path ?? null,
|
|
162
|
-
packageHash: hashTree(packageFiles),
|
|
163
|
-
skills: skills.sort((left, right) => left.name.localeCompare(right.name)),
|
|
164
|
-
validationStatus: validationMessages.length === 0 ? "valid" : "error",
|
|
165
|
-
validationMessages
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
function previewSkill(skillPackage, contents) {
|
|
169
|
-
const parsed = parseProfileSkillMarkdown(contents);
|
|
170
|
-
const directoryName = skillPackage.root === "." ? null : path.posix.basename(skillPackage.root);
|
|
171
|
-
const name = parsed.name ?? directoryName ?? "invalid-skill";
|
|
172
|
-
const messages = [...parsed.messages];
|
|
173
|
-
if (!SKILL_NAME_PATTERN.test(name)) messages.push("Skill name must be lowercase kebab-case.");
|
|
174
|
-
if (directoryName && name !== directoryName) {
|
|
175
|
-
messages.push(`Skill name must match its directory name (${directoryName}).`);
|
|
176
|
-
}
|
|
177
|
-
const resourceFiles = skillPackage.files.filter((file) => file.path !== skillPackage.entry.path).map((file) => relativeSkillPath(skillPackage.root, file.path)).sort();
|
|
178
|
-
return {
|
|
179
|
-
name,
|
|
180
|
-
description: parsed.description ?? "",
|
|
181
|
-
relativePath: skillPackage.root === "." ? "SKILL.md" : `${skillPackage.root}/SKILL.md`,
|
|
182
|
-
sourcePath: "",
|
|
183
|
-
charCount: contents.length,
|
|
184
|
-
sourceHash: createHash("sha256").update(contents).digest("hex"),
|
|
185
|
-
resourceFiles,
|
|
186
|
-
validationStatus: messages.length === 0 ? "valid" : "error",
|
|
187
|
-
validationMessages: [...new Set(messages)]
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
function discoverSkillPackages(tree) {
|
|
191
|
-
const entries = [];
|
|
192
|
-
const rootEntry = tree.find((entry) => entry.type === "blob" && entry.path === "SKILL.md");
|
|
193
|
-
if (rootEntry) entries.push({ root: ".", entry: rootEntry });
|
|
194
|
-
const skillEntries = tree.filter((entry) => entry.type === "blob" && entry.path.endsWith("/SKILL.md"));
|
|
195
|
-
const standardEntries = skillEntries.filter((entry) => isStandardAgentSkillsPath(entry.path));
|
|
196
|
-
const discoveredEntries = standardEntries.length > 0 ? standardEntries : skillEntries;
|
|
197
|
-
const roots = new Set(discoveredEntries.map((entry) => path.posix.dirname(entry.path)));
|
|
198
|
-
for (const entry of discoveredEntries) {
|
|
199
|
-
const root = path.posix.dirname(entry.path);
|
|
200
|
-
if ([...roots].some((candidate) => candidate !== root && root.startsWith(`${candidate}/`))) continue;
|
|
201
|
-
entries.push({ root, entry });
|
|
202
|
-
}
|
|
203
|
-
return entries.map(({ root, entry }) => {
|
|
204
|
-
const files = tree.filter((candidate) => {
|
|
205
|
-
if (candidate.type !== "blob") return false;
|
|
206
|
-
if (root !== ".") return candidate.path.startsWith(`${root}/`);
|
|
207
|
-
if (candidate.path === "SKILL.md") return true;
|
|
208
|
-
const firstSegment = candidate.path.split("/", 1)[0] ?? "";
|
|
209
|
-
return ROOT_SKILL_RESOURCE_DIRECTORIES.has(firstSegment);
|
|
210
|
-
});
|
|
211
|
-
const unsafe = tree.find(
|
|
212
|
-
(candidate) => candidate.type !== "blob" && candidate.type !== "tree" && (root === "." ? candidate.path === "SKILL.md" || ROOT_SKILL_RESOURCE_DIRECTORIES.has(candidate.path.split("/", 1)[0] ?? "") : candidate.path.startsWith(`${root}/`))
|
|
213
|
-
);
|
|
214
|
-
const symlink = files.find((candidate) => candidate.mode === "120000");
|
|
215
|
-
if (unsafe || symlink) {
|
|
216
|
-
throw new GithubExtensionError(`Skill package ${root} contains a symlink or submodule.`, {
|
|
217
|
-
code: "extension_unsafe_entry"
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
return { root, entry, files };
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
function isStandardAgentSkillsPath(skillPath) {
|
|
224
|
-
const segments = skillPath.split("/");
|
|
225
|
-
if (segments.at(-1) !== "SKILL.md") return false;
|
|
226
|
-
const skillDirectorySegments = segments.slice(0, -1);
|
|
227
|
-
if (skillDirectorySegments.length < 2) return false;
|
|
228
|
-
const skillsIndex = skillDirectorySegments.lastIndexOf("skills");
|
|
229
|
-
if (skillsIndex < 0 || skillsIndex > 2) return false;
|
|
230
|
-
const containerPrefix = skillDirectorySegments.slice(0, skillsIndex);
|
|
231
|
-
const nestedSkillPath = skillDirectorySegments.slice(skillsIndex + 1);
|
|
232
|
-
if (nestedSkillPath.length < 1 || nestedSkillPath.length > 2) return false;
|
|
233
|
-
if (skillsIndex === 0) return true;
|
|
234
|
-
const first = containerPrefix[0] ?? "";
|
|
235
|
-
return first.startsWith(".") || first === "agent" || first === "data";
|
|
236
|
-
}
|
|
237
|
-
function filesToDownload(source) {
|
|
238
|
-
const byPath = /* @__PURE__ */ new Map();
|
|
239
|
-
if (source.readme) byPath.set(source.readme.path, source.readme);
|
|
240
|
-
for (const skillPackage of source.skillPackages) {
|
|
241
|
-
for (const file of skillPackage.files) byPath.set(file.path, file);
|
|
242
|
-
}
|
|
243
|
-
return [...byPath.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
244
|
-
}
|
|
245
|
-
async function downloadFiles(input) {
|
|
246
|
-
let downloadedBytes = 0;
|
|
247
|
-
await mapConcurrent(input.resolution.files, DOWNLOAD_CONCURRENCY, async (file) => {
|
|
248
|
-
const bytes = await downloadBytes({
|
|
249
|
-
owner: input.resolution.preview.owner,
|
|
250
|
-
repo: input.resolution.preview.repo,
|
|
251
|
-
commit: input.resolution.preview.resolvedCommit,
|
|
252
|
-
file,
|
|
253
|
-
fetchImpl: input.fetchImpl,
|
|
254
|
-
githubToken: input.githubToken
|
|
255
|
-
});
|
|
256
|
-
downloadedBytes += bytes.byteLength;
|
|
257
|
-
if (downloadedBytes > MAX_EXTENSION_BYTES) {
|
|
258
|
-
throw new GithubExtensionError("Extension download exceeds the maximum allowed size.", {
|
|
259
|
-
code: "extension_too_large"
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
await input.writeFile(file.path, bytes, file.mode === "100755");
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
async function downloadText(source, file, fetchImpl, githubToken) {
|
|
266
|
-
const bytes = await downloadBytes({
|
|
267
|
-
owner: source.identity.owner,
|
|
268
|
-
repo: source.identity.repo,
|
|
269
|
-
commit: source.resolvedCommit,
|
|
270
|
-
file,
|
|
271
|
-
fetchImpl,
|
|
272
|
-
githubToken
|
|
273
|
-
});
|
|
274
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
275
|
-
}
|
|
276
|
-
async function downloadBytes(input) {
|
|
277
|
-
const encodedPath = input.file.path.split("/").map(encodeURIComponent).join("/");
|
|
278
|
-
const url = `https://raw.githubusercontent.com/${input.owner}/${input.repo}/${input.commit}/${encodedPath}`;
|
|
279
|
-
const response = await input.fetchImpl(url, {
|
|
280
|
-
headers: githubHeaders(input.githubToken, "application/vnd.github.raw"),
|
|
281
|
-
redirect: "follow"
|
|
282
|
-
});
|
|
283
|
-
if (!response.ok) {
|
|
284
|
-
throw new GithubExtensionError(`GitHub file download failed (${response.status}): ${input.file.path}`, {
|
|
285
|
-
code: "github_download_failed",
|
|
286
|
-
status: response.status
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
const contentLength = Number(response.headers.get("content-length"));
|
|
290
|
-
if (Number.isFinite(contentLength) && contentLength > MAX_EXTENSION_FILE_BYTES) {
|
|
291
|
-
throw new GithubExtensionError(`Extension file is too large: ${input.file.path}`, {
|
|
292
|
-
code: "extension_file_too_large"
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
296
|
-
if (bytes.byteLength > MAX_EXTENSION_FILE_BYTES) {
|
|
297
|
-
throw new GithubExtensionError(`Extension file is too large: ${input.file.path}`, {
|
|
298
|
-
code: "extension_file_too_large"
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
return bytes;
|
|
302
|
-
}
|
|
303
|
-
async function githubJson(fetchImpl, githubToken, pathname) {
|
|
304
|
-
const response = await fetchImpl(`https://api.github.com${pathname}`, {
|
|
305
|
-
headers: githubHeaders(githubToken, "application/vnd.github+json")
|
|
306
|
-
});
|
|
307
|
-
if (!response.ok) {
|
|
308
|
-
const details = await response.json().catch(() => null);
|
|
309
|
-
const detail = typeof details?.message === "string" ? `: ${details.message}` : "";
|
|
310
|
-
throw new GithubExtensionError(`GitHub request failed (${response.status})${detail}`, {
|
|
311
|
-
code: response.status === 404 ? "github_repository_not_found" : "github_request_failed",
|
|
312
|
-
status: response.status
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
return await response.json();
|
|
316
|
-
}
|
|
317
|
-
function githubHeaders(token, accept) {
|
|
318
|
-
const headers = new Headers({
|
|
319
|
-
Accept: accept,
|
|
320
|
-
"User-Agent": "openpond-extension-installer",
|
|
321
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
322
|
-
});
|
|
323
|
-
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
324
|
-
return headers;
|
|
325
|
-
}
|
|
326
|
-
function validateDownloadLimits(skillPackages) {
|
|
327
|
-
const files = /* @__PURE__ */ new Map();
|
|
328
|
-
for (const skillPackage of skillPackages) {
|
|
329
|
-
for (const file of skillPackage.files) files.set(file.path, file);
|
|
330
|
-
}
|
|
331
|
-
if (files.size > MAX_EXTENSION_FILES) {
|
|
332
|
-
throw new GithubExtensionError(`Extension contains more than ${MAX_EXTENSION_FILES} skill files.`, {
|
|
333
|
-
code: "extension_too_many_files"
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
let totalBytes = 0;
|
|
337
|
-
for (const file of files.values()) {
|
|
338
|
-
if ((file.size ?? 0) > MAX_EXTENSION_FILE_BYTES) {
|
|
339
|
-
throw new GithubExtensionError(`Extension file is too large: ${file.path}`, {
|
|
340
|
-
code: "extension_file_too_large"
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
totalBytes += file.size ?? 0;
|
|
344
|
-
}
|
|
345
|
-
if (totalBytes > MAX_EXTENSION_BYTES) {
|
|
346
|
-
throw new GithubExtensionError("Extension exceeds the maximum allowed size.", {
|
|
347
|
-
code: "extension_too_large"
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
function validTreeEntry(value) {
|
|
352
|
-
if (!value || typeof value !== "object") return false;
|
|
353
|
-
const entry = value;
|
|
354
|
-
return typeof entry.path === "string" && !entry.path.startsWith("/") && !entry.path.split("/").includes("..") && typeof entry.mode === "string" && (entry.type === "blob" || entry.type === "tree" || entry.type === "commit") && typeof entry.sha === "string";
|
|
355
|
-
}
|
|
356
|
-
function hashTree(files) {
|
|
357
|
-
const hash = createHash("sha256");
|
|
358
|
-
for (const file of files) hash.update(`${file.path}\0${file.sha}\0${file.size ?? ""}
|
|
359
|
-
`);
|
|
360
|
-
return hash.digest("hex");
|
|
361
|
-
}
|
|
362
|
-
function relativeSkillPath(root, filePath) {
|
|
363
|
-
return root === "." ? filePath : filePath.slice(root.length + 1);
|
|
364
|
-
}
|
|
365
|
-
function duplicateValues(values) {
|
|
366
|
-
const seen = /* @__PURE__ */ new Set();
|
|
367
|
-
const duplicates = /* @__PURE__ */ new Set();
|
|
368
|
-
for (const value of values) {
|
|
369
|
-
if (seen.has(value)) duplicates.add(value);
|
|
370
|
-
seen.add(value);
|
|
371
|
-
}
|
|
372
|
-
return duplicates;
|
|
373
|
-
}
|
|
374
|
-
async function mapConcurrent(values, concurrency, mapper) {
|
|
375
|
-
const results = new Array(values.length);
|
|
376
|
-
let nextIndex = 0;
|
|
377
|
-
async function worker() {
|
|
378
|
-
while (nextIndex < values.length) {
|
|
379
|
-
const index = nextIndex;
|
|
380
|
-
nextIndex += 1;
|
|
381
|
-
results[index] = await mapper(values[index]);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, worker));
|
|
385
|
-
return results;
|
|
386
|
-
}
|
|
387
|
-
function invalidSource(source) {
|
|
388
|
-
return new GithubExtensionError(
|
|
389
|
-
`Invalid GitHub extension source: ${source}. Use owner/repo or https://github.com/owner/repo.`,
|
|
390
|
-
{ code: "github_invalid_source" }
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// ../../packages/cloud/src/extensions/extension-manager.ts
|
|
395
|
-
var REGISTRY_SCHEMA_VERSION = 1;
|
|
396
|
-
var OPERATION_LOCK_STALE_MS = 2 * 60 * 1e3;
|
|
397
|
-
var OPERATION_LOCK_TIMEOUT_MS = 15e3;
|
|
398
|
-
function createGithubExtensionManager(options = {}) {
|
|
399
|
-
const rootPath = path2.resolve(options.rootPath ?? path2.join(openPondConfigDirectory(), "extensions"));
|
|
400
|
-
const registryPath = path2.join(rootPath, "registry.json");
|
|
401
|
-
const github = createGithubSourceClient({
|
|
402
|
-
fetch: options.fetch,
|
|
403
|
-
githubToken: options.githubToken
|
|
404
|
-
});
|
|
405
|
-
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
406
|
-
async function list() {
|
|
407
|
-
try {
|
|
408
|
-
const registry = await readRegistry(registryPath);
|
|
409
|
-
const extensions = await Promise.all(registry.extensions.map((extension) => hydrateExtension(rootPath, extension)));
|
|
410
|
-
markInstalledSkillConflicts(extensions, await reservedSkillNames(options.reservedSkillNames));
|
|
411
|
-
return {
|
|
412
|
-
rootPath,
|
|
413
|
-
registryPath,
|
|
414
|
-
extensions: extensions.sort(extensionSort),
|
|
415
|
-
error: null
|
|
416
|
-
};
|
|
417
|
-
} catch (error) {
|
|
418
|
-
return {
|
|
419
|
-
rootPath,
|
|
420
|
-
registryPath,
|
|
421
|
-
extensions: [],
|
|
422
|
-
error: error instanceof Error ? error.message : String(error)
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
async function preview(request) {
|
|
427
|
-
const resolution = await github.resolve(request);
|
|
428
|
-
const registry = await readRegistry(registryPath);
|
|
429
|
-
const existing = registry.extensions.find((extension) => extension.id === resolution.preview.id) ?? null;
|
|
430
|
-
assertSkillNamesAvailable(
|
|
431
|
-
resolution.preview,
|
|
432
|
-
registry.extensions.filter((extension) => extension.id !== existing?.id),
|
|
433
|
-
await reservedSkillNames(options.reservedSkillNames)
|
|
434
|
-
);
|
|
435
|
-
return resolution.preview;
|
|
436
|
-
}
|
|
437
|
-
async function add(request) {
|
|
438
|
-
const identity = parseGithubExtensionSource(request.source);
|
|
439
|
-
return withExtensionLock(rootPath, identity.id, async () => {
|
|
440
|
-
const registry = await readRegistry(registryPath);
|
|
441
|
-
if (registry.extensions.some((extension) => extension.id === identity.id)) {
|
|
442
|
-
throw new GithubExtensionError(`${identity.owner}/${identity.repo} is already installed.`, {
|
|
443
|
-
code: "extension_already_installed",
|
|
444
|
-
status: 409
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
const resolution = await github.resolve(request);
|
|
448
|
-
assertInstallable(resolution.preview);
|
|
449
|
-
assertSkillNamesAvailable(
|
|
450
|
-
resolution.preview,
|
|
451
|
-
registry.extensions,
|
|
452
|
-
await reservedSkillNames(options.reservedSkillNames)
|
|
453
|
-
);
|
|
454
|
-
const timestamp = now().toISOString();
|
|
455
|
-
const installed = await materializeExtension({
|
|
456
|
-
rootPath,
|
|
457
|
-
resolution,
|
|
458
|
-
installedAt: timestamp,
|
|
459
|
-
updatedAt: timestamp,
|
|
460
|
-
github
|
|
461
|
-
});
|
|
462
|
-
try {
|
|
463
|
-
const reserved = await reservedSkillNames(options.reservedSkillNames);
|
|
464
|
-
await writeRegistry(registryPath, now, (current) => ({
|
|
465
|
-
...current,
|
|
466
|
-
extensions: appendInstalledExtension(current, installed, reserved)
|
|
467
|
-
}));
|
|
468
|
-
await finalizeMaterialization(rootPath, installed.id);
|
|
469
|
-
return installed;
|
|
470
|
-
} catch (error) {
|
|
471
|
-
await rollbackMaterialization(rootPath, installed.id);
|
|
472
|
-
throw error;
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
async function update(request) {
|
|
477
|
-
const identity = parseGithubExtensionSource(request.source);
|
|
478
|
-
return withExtensionLock(rootPath, identity.id, async () => {
|
|
479
|
-
const registry = await readRegistry(registryPath);
|
|
480
|
-
const existing = requireInstalled(registry, identity.id);
|
|
481
|
-
const resolution = await github.resolve({
|
|
482
|
-
source: existing.repositoryUrl,
|
|
483
|
-
ref: request.ref?.trim() || existing.requestedRef
|
|
484
|
-
});
|
|
485
|
-
assertInstallable(resolution.preview);
|
|
486
|
-
assertSkillNamesAvailable(
|
|
487
|
-
resolution.preview,
|
|
488
|
-
registry.extensions.filter((extension) => extension.id !== existing.id),
|
|
489
|
-
await reservedSkillNames(options.reservedSkillNames)
|
|
490
|
-
);
|
|
491
|
-
if (existing.resolvedCommit === resolution.preview.resolvedCommit && existing.packageHash === resolution.preview.packageHash && existing.requestedRef === resolution.preview.requestedRef) {
|
|
492
|
-
return hydrateExtension(rootPath, existing);
|
|
493
|
-
}
|
|
494
|
-
const installed = await materializeExtension({
|
|
495
|
-
rootPath,
|
|
496
|
-
resolution,
|
|
497
|
-
installedAt: existing.installedAt,
|
|
498
|
-
updatedAt: now().toISOString(),
|
|
499
|
-
github
|
|
500
|
-
});
|
|
501
|
-
try {
|
|
502
|
-
const reserved = await reservedSkillNames(options.reservedSkillNames);
|
|
503
|
-
await writeRegistry(registryPath, now, (current) => ({
|
|
504
|
-
...current,
|
|
505
|
-
extensions: replaceInstalledExtension(current, installed, reserved)
|
|
506
|
-
}));
|
|
507
|
-
await finalizeMaterialization(rootPath, installed.id);
|
|
508
|
-
return installed;
|
|
509
|
-
} catch (error) {
|
|
510
|
-
await rollbackMaterialization(rootPath, installed.id);
|
|
511
|
-
throw error;
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
async function updateAll() {
|
|
516
|
-
const catalog = await list();
|
|
517
|
-
if (catalog.error) throw new GithubExtensionError(catalog.error, { code: "extension_registry_error" });
|
|
518
|
-
const result = { updated: [], unchanged: [], failed: [] };
|
|
519
|
-
for (const extension of catalog.extensions) {
|
|
520
|
-
try {
|
|
521
|
-
const next = await update({ source: extension.repositoryUrl, ref: extension.requestedRef });
|
|
522
|
-
if (next.resolvedCommit === extension.resolvedCommit && next.packageHash === extension.packageHash) {
|
|
523
|
-
result.unchanged.push(next);
|
|
524
|
-
} else {
|
|
525
|
-
result.updated.push(next);
|
|
526
|
-
}
|
|
527
|
-
} catch (error) {
|
|
528
|
-
result.failed.push({
|
|
529
|
-
id: extension.id,
|
|
530
|
-
error: error instanceof Error ? error.message : String(error)
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
return result;
|
|
535
|
-
}
|
|
536
|
-
async function inspect(source) {
|
|
537
|
-
const identity = parseGithubExtensionSource(source);
|
|
538
|
-
const registry = await readRegistry(registryPath);
|
|
539
|
-
return hydrateExtension(rootPath, requireInstalled(registry, identity.id));
|
|
540
|
-
}
|
|
541
|
-
async function remove(source) {
|
|
542
|
-
const identity = parseGithubExtensionSource(source);
|
|
543
|
-
return withExtensionLock(rootPath, identity.id, async () => {
|
|
544
|
-
const registry = await readRegistry(registryPath);
|
|
545
|
-
const existing = requireInstalled(registry, identity.id);
|
|
546
|
-
const target = extensionDirectory(rootPath, identity.owner, identity.repo);
|
|
547
|
-
const trash = `${target}.${randomUUID()}.remove`;
|
|
548
|
-
if (existsSync(target)) await rename(target, trash);
|
|
549
|
-
try {
|
|
550
|
-
await writeRegistry(registryPath, now, (current) => ({
|
|
551
|
-
...current,
|
|
552
|
-
extensions: current.extensions.filter((extension) => extension.id !== existing.id)
|
|
553
|
-
}));
|
|
554
|
-
await rm(trash, { recursive: true, force: true });
|
|
555
|
-
return existing;
|
|
556
|
-
} catch (error) {
|
|
557
|
-
if (existsSync(trash) && !existsSync(target)) await rename(trash, target);
|
|
558
|
-
throw error;
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
async function readSkill(nameInput) {
|
|
563
|
-
const name = nameInput.trim();
|
|
564
|
-
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) {
|
|
565
|
-
throw new GithubExtensionError("Extension skill name must be lowercase kebab-case.", {
|
|
566
|
-
code: "extension_skill_invalid_name"
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
const catalog = await list();
|
|
570
|
-
if (catalog.error) {
|
|
571
|
-
throw new GithubExtensionError(catalog.error, { code: "extension_registry_error" });
|
|
572
|
-
}
|
|
573
|
-
const matches = catalog.extensions.flatMap(
|
|
574
|
-
(extension) => extension.skills.filter((skill) => skill.name === name).map((skill) => ({ extension, skill }))
|
|
575
|
-
);
|
|
576
|
-
if (matches.length === 0) {
|
|
577
|
-
throw new GithubExtensionError(`Extension skill not found: ${name}`, {
|
|
578
|
-
code: "extension_skill_not_found",
|
|
579
|
-
status: 404
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
if (matches.length > 1) {
|
|
583
|
-
throw new GithubExtensionError(`Extension skill name is ambiguous: ${name}`, {
|
|
584
|
-
code: "extension_skill_conflict",
|
|
585
|
-
status: 409
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
const match = matches[0];
|
|
589
|
-
if (match.extension.validationStatus !== "valid" || match.skill.validationStatus !== "valid") {
|
|
590
|
-
throw new GithubExtensionError(`Extension skill ${name} is not valid.`, {
|
|
591
|
-
code: "extension_skill_invalid"
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
const absolutePath = safeDestination(match.extension.sourcePath, match.skill.relativePath);
|
|
595
|
-
const markdown = await readFile(absolutePath, "utf8");
|
|
596
|
-
const sourceHash = createHash2("sha256").update(markdown).digest("hex");
|
|
597
|
-
if (sourceHash !== match.skill.sourceHash) {
|
|
598
|
-
throw new GithubExtensionError(
|
|
599
|
-
`Extension skill ${name} changed on disk. Update or reinstall its extension before using it.`,
|
|
600
|
-
{ code: "extension_skill_modified" }
|
|
601
|
-
);
|
|
602
|
-
}
|
|
603
|
-
const parsed = parseProfileSkillMarkdown(markdown);
|
|
604
|
-
if (parsed.messages.length > 0 || parsed.name !== name || !parsed.description) {
|
|
605
|
-
throw new GithubExtensionError(
|
|
606
|
-
`Extension skill ${name} is not valid. ${parsed.messages.join(" ")}`.trim(),
|
|
607
|
-
{ code: "extension_skill_invalid" }
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
|
-
return {
|
|
611
|
-
name,
|
|
612
|
-
description: parsed.description,
|
|
613
|
-
body: parsed.body,
|
|
614
|
-
path: match.skill.relativePath,
|
|
615
|
-
sourcePath: match.extension.sourcePath,
|
|
616
|
-
sourceHash,
|
|
617
|
-
charCount: markdown.length,
|
|
618
|
-
packagePath: path2.dirname(absolutePath),
|
|
619
|
-
resourceFiles: match.skill.resourceFiles
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
return { rootPath, registryPath, list, preview, add, update, updateAll, inspect, remove, readSkill };
|
|
623
|
-
}
|
|
624
|
-
async function materializeExtension(input) {
|
|
625
|
-
const preview = input.resolution.preview;
|
|
626
|
-
const target = extensionDirectory(input.rootPath, preview.owner, preview.repo);
|
|
627
|
-
const temporary = `${target}.${randomUUID()}.install`;
|
|
628
|
-
const backup = backupDirectory(input.rootPath, preview.id);
|
|
629
|
-
const current = path2.join(temporary, "current");
|
|
630
|
-
await rm(temporary, { recursive: true, force: true });
|
|
631
|
-
await rm(backup, { recursive: true, force: true });
|
|
632
|
-
await mkdir(current, { recursive: true, mode: 448 });
|
|
633
|
-
try {
|
|
634
|
-
await input.github.download(input.resolution, async (relativePath, bytes, executable) => {
|
|
635
|
-
const destination = safeDestination(current, relativePath);
|
|
636
|
-
await mkdir(path2.dirname(destination), { recursive: true, mode: 448 });
|
|
637
|
-
await writeFile(destination, bytes, { mode: executable ? 448 : 384 });
|
|
638
|
-
});
|
|
639
|
-
const extension = installedExtension(preview, target, input.installedAt, input.updatedAt);
|
|
640
|
-
await writeFile(
|
|
641
|
-
path2.join(temporary, "extension.json"),
|
|
642
|
-
`${JSON.stringify(extension, null, 2)}
|
|
643
|
-
`,
|
|
644
|
-
{ mode: 384 }
|
|
645
|
-
);
|
|
646
|
-
await mkdir(path2.dirname(target), { recursive: true, mode: 448 });
|
|
647
|
-
if (existsSync(target)) await rename(target, backup);
|
|
648
|
-
await rename(temporary, target);
|
|
649
|
-
return extension;
|
|
650
|
-
} catch (error) {
|
|
651
|
-
await rm(temporary, { recursive: true, force: true });
|
|
652
|
-
if (existsSync(backup) && !existsSync(target)) await rename(backup, target);
|
|
653
|
-
throw error;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
function installedExtension(preview, target, installedAt, updatedAt) {
|
|
657
|
-
const sourcePath = path2.join(target, "current");
|
|
658
|
-
return {
|
|
659
|
-
...preview,
|
|
660
|
-
sourcePath,
|
|
661
|
-
readmePath: preview.readmePath ? path2.join(sourcePath, preview.readmePath) : null,
|
|
662
|
-
installedAt,
|
|
663
|
-
updatedAt,
|
|
664
|
-
skills: preview.skills.map((skill) => ({
|
|
665
|
-
...skill,
|
|
666
|
-
sourcePath: path2.join(sourcePath, skill.relativePath)
|
|
667
|
-
}))
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
async function finalizeMaterialization(rootPath, id) {
|
|
671
|
-
await rm(backupDirectory(rootPath, id), { recursive: true, force: true });
|
|
672
|
-
}
|
|
673
|
-
async function rollbackMaterialization(rootPath, id) {
|
|
674
|
-
const identity = parseGithubExtensionSource(id.replace(/^github:/, ""));
|
|
675
|
-
const target = extensionDirectory(rootPath, identity.owner, identity.repo);
|
|
676
|
-
const backup = backupDirectory(rootPath, id);
|
|
677
|
-
await rm(target, { recursive: true, force: true });
|
|
678
|
-
if (existsSync(backup)) await rename(backup, target);
|
|
679
|
-
}
|
|
680
|
-
async function hydrateExtension(rootPath, extension) {
|
|
681
|
-
const target = extensionDirectory(rootPath, extension.owner, extension.repo);
|
|
682
|
-
const sourcePath = path2.join(target, "current");
|
|
683
|
-
const present = (await stat(path2.join(target, "extension.json")).catch(() => null))?.isFile() ?? false;
|
|
684
|
-
const validationMessages = extension.validationMessages.filter((message) => message !== "Installed files are missing.");
|
|
685
|
-
if (!present) validationMessages.push("Installed files are missing.");
|
|
686
|
-
return {
|
|
687
|
-
...extension,
|
|
688
|
-
sourcePath,
|
|
689
|
-
readmePath: extension.readmePath ? path2.join(sourcePath, path2.basename(extension.readmePath)) : null,
|
|
690
|
-
skills: extension.skills.map((skill) => ({
|
|
691
|
-
...skill,
|
|
692
|
-
sourcePath: path2.join(sourcePath, skill.relativePath)
|
|
693
|
-
})),
|
|
694
|
-
validationStatus: validationMessages.length === 0 ? "valid" : "error",
|
|
695
|
-
validationMessages
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
function assertInstallable(preview) {
|
|
699
|
-
if (preview.validationStatus === "valid") return;
|
|
700
|
-
throw new GithubExtensionError(
|
|
701
|
-
`Extension contains invalid skills: ${preview.validationMessages.join(" ")}`,
|
|
702
|
-
{ code: "extension_invalid" }
|
|
703
|
-
);
|
|
704
|
-
}
|
|
705
|
-
function assertSkillNamesAvailable(preview, installed, reserved) {
|
|
706
|
-
const owners = /* @__PURE__ */ new Map();
|
|
707
|
-
for (const name of reserved) owners.set(name, "a shipped OpenPond skill");
|
|
708
|
-
for (const extension of installed) {
|
|
709
|
-
for (const skill of extension.skills) owners.set(skill.name, extension.id);
|
|
710
|
-
}
|
|
711
|
-
const conflicts = preview.skills.map((skill) => ({ name: skill.name, owner: owners.get(skill.name) })).filter((entry) => Boolean(entry.owner));
|
|
712
|
-
if (conflicts.length === 0) return;
|
|
713
|
-
throw new GithubExtensionError(
|
|
714
|
-
`Skill name conflict: ${conflicts.map((entry) => `${entry.name} (${entry.owner})`).join(", ")}.`,
|
|
715
|
-
{ code: "extension_skill_conflict", status: 409 }
|
|
716
|
-
);
|
|
717
|
-
}
|
|
718
|
-
function appendInstalledExtension(registry, installed, reserved) {
|
|
719
|
-
if (registry.extensions.some((extension) => extension.id === installed.id)) {
|
|
720
|
-
throw new GithubExtensionError(`${installed.owner}/${installed.repo} is already installed.`, {
|
|
721
|
-
code: "extension_already_installed",
|
|
722
|
-
status: 409
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
assertSkillNamesAvailable(installed, registry.extensions, reserved);
|
|
726
|
-
return [...registry.extensions, installed].sort(extensionSort);
|
|
727
|
-
}
|
|
728
|
-
function replaceInstalledExtension(registry, installed, reserved) {
|
|
729
|
-
requireInstalled(registry, installed.id);
|
|
730
|
-
assertSkillNamesAvailable(
|
|
731
|
-
installed,
|
|
732
|
-
registry.extensions.filter((extension) => extension.id !== installed.id),
|
|
733
|
-
reserved
|
|
734
|
-
);
|
|
735
|
-
return registry.extensions.map((extension) => extension.id === installed.id ? installed : extension).sort(extensionSort);
|
|
736
|
-
}
|
|
737
|
-
function markInstalledSkillConflicts(extensions, reserved) {
|
|
738
|
-
const owners = /* @__PURE__ */ new Map();
|
|
739
|
-
for (const name of reserved) owners.set(name, "built-in");
|
|
740
|
-
for (const extension of extensions) {
|
|
741
|
-
for (const skill of extension.skills) {
|
|
742
|
-
const owner = owners.get(skill.name);
|
|
743
|
-
if (!owner) {
|
|
744
|
-
owners.set(skill.name, extension.id);
|
|
745
|
-
continue;
|
|
746
|
-
}
|
|
747
|
-
const message = `Skill name conflicts with ${owner}: ${skill.name}`;
|
|
748
|
-
skill.validationStatus = "error";
|
|
749
|
-
skill.validationMessages = [.../* @__PURE__ */ new Set([...skill.validationMessages, message])];
|
|
750
|
-
extension.validationStatus = "error";
|
|
751
|
-
extension.validationMessages = [.../* @__PURE__ */ new Set([...extension.validationMessages, message])];
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
async function reservedSkillNames(source) {
|
|
756
|
-
const values = typeof source === "function" ? await source() : source ?? [];
|
|
757
|
-
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
758
|
-
}
|
|
759
|
-
function requireInstalled(registry, id) {
|
|
760
|
-
const extension = registry.extensions.find((candidate) => candidate.id === id);
|
|
761
|
-
if (extension) return extension;
|
|
762
|
-
throw new GithubExtensionError(`${id.replace(/^github:/, "")} is not installed.`, {
|
|
763
|
-
code: "extension_not_installed",
|
|
764
|
-
status: 404
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
async function readRegistry(registryPath) {
|
|
768
|
-
try {
|
|
769
|
-
const parsed = JSON.parse(await readFile(registryPath, "utf8"));
|
|
770
|
-
if (parsed.schemaVersion !== REGISTRY_SCHEMA_VERSION || !Array.isArray(parsed.extensions)) {
|
|
771
|
-
throw new Error("Extension registry has an unsupported format.");
|
|
772
|
-
}
|
|
773
|
-
return {
|
|
774
|
-
schemaVersion: REGISTRY_SCHEMA_VERSION,
|
|
775
|
-
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : null,
|
|
776
|
-
extensions: parsed.extensions.filter(isStoredExtension)
|
|
777
|
-
};
|
|
778
|
-
} catch (error) {
|
|
779
|
-
if (error.code === "ENOENT") return emptyRegistry();
|
|
780
|
-
throw error;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
async function writeRegistry(registryPath, now, update) {
|
|
784
|
-
await updatePrivateJsonFile(registryPath, emptyRegistry, (current) => ({
|
|
785
|
-
...update(normalizeRegistry(current)),
|
|
786
|
-
schemaVersion: REGISTRY_SCHEMA_VERSION,
|
|
787
|
-
updatedAt: now().toISOString()
|
|
788
|
-
}));
|
|
789
|
-
}
|
|
790
|
-
function normalizeRegistry(value) {
|
|
791
|
-
return {
|
|
792
|
-
schemaVersion: REGISTRY_SCHEMA_VERSION,
|
|
793
|
-
updatedAt: typeof value?.updatedAt === "string" ? value.updatedAt : null,
|
|
794
|
-
extensions: Array.isArray(value?.extensions) ? value.extensions.filter(isStoredExtension) : []
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
function emptyRegistry() {
|
|
798
|
-
return { schemaVersion: REGISTRY_SCHEMA_VERSION, updatedAt: null, extensions: [] };
|
|
799
|
-
}
|
|
800
|
-
function isStoredExtension(value) {
|
|
801
|
-
if (!value || typeof value !== "object") return false;
|
|
802
|
-
const extension = value;
|
|
803
|
-
return extension.source === "github" && typeof extension.id === "string" && typeof extension.owner === "string" && typeof extension.repo === "string" && typeof extension.repositoryUrl === "string" && typeof extension.requestedRef === "string" && typeof extension.resolvedCommit === "string" && Array.isArray(extension.skills);
|
|
804
|
-
}
|
|
805
|
-
function safeDestination(root, relativePath) {
|
|
806
|
-
if (path2.isAbsolute(relativePath) || relativePath.split(/[\\/]/).includes("..")) {
|
|
807
|
-
throw new GithubExtensionError(`Unsafe extension path: ${relativePath}`, {
|
|
808
|
-
code: "extension_unsafe_path"
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
const destination = path2.resolve(root, ...relativePath.split("/"));
|
|
812
|
-
const prefix = `${path2.resolve(root)}${path2.sep}`;
|
|
813
|
-
if (!destination.startsWith(prefix)) {
|
|
814
|
-
throw new GithubExtensionError(`Unsafe extension path: ${relativePath}`, {
|
|
815
|
-
code: "extension_unsafe_path"
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
return destination;
|
|
819
|
-
}
|
|
820
|
-
function extensionDirectory(rootPath, owner, repo) {
|
|
821
|
-
return path2.join(rootPath, "github", owner, repo);
|
|
822
|
-
}
|
|
823
|
-
function backupDirectory(rootPath, id) {
|
|
824
|
-
const identity = parseGithubExtensionSource(id.replace(/^github:/, ""));
|
|
825
|
-
return `${extensionDirectory(rootPath, identity.owner, identity.repo)}.previous`;
|
|
826
|
-
}
|
|
827
|
-
async function withExtensionLock(rootPath, id, operation) {
|
|
828
|
-
const safeId = id.replace(/[^a-z0-9.-]+/gi, "-");
|
|
829
|
-
const lockPath = path2.join(rootPath, ".locks", `${safeId}.lock`);
|
|
830
|
-
await mkdir(path2.dirname(lockPath), { recursive: true, mode: 448 });
|
|
831
|
-
const deadline = Date.now() + OPERATION_LOCK_TIMEOUT_MS;
|
|
832
|
-
while (true) {
|
|
833
|
-
try {
|
|
834
|
-
await mkdir(lockPath, { mode: 448 });
|
|
835
|
-
break;
|
|
836
|
-
} catch (error) {
|
|
837
|
-
if (error.code !== "EEXIST") throw error;
|
|
838
|
-
const lockStat = await stat(lockPath).catch(() => null);
|
|
839
|
-
if (lockStat && Date.now() - lockStat.mtimeMs > OPERATION_LOCK_STALE_MS) {
|
|
840
|
-
await rm(lockPath, { recursive: true, force: true });
|
|
841
|
-
continue;
|
|
842
|
-
}
|
|
843
|
-
if (Date.now() >= deadline) {
|
|
844
|
-
throw new GithubExtensionError(`Timed out waiting for extension operation: ${id}`, {
|
|
845
|
-
code: "extension_operation_busy",
|
|
846
|
-
status: 409
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
try {
|
|
853
|
-
return await operation();
|
|
854
|
-
} finally {
|
|
855
|
-
await rm(lockPath, { recursive: true, force: true });
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
function extensionSort(left, right) {
|
|
859
|
-
return left.id.localeCompare(right.id);
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
export {
|
|
863
|
-
createGithubExtensionManager
|
|
864
|
-
};
|