openpond 0.0.30-bootstrap.0
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/CHANGELOG.md +128 -0
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/RELEASE.md +20 -0
- package/dist/api/core.d.ts +22 -0
- package/dist/api/deployments.d.ts +30 -0
- package/dist/api/types.d.ts +624 -0
- package/dist/api.d.ts +90 -0
- package/dist/cache.d.ts +22 -0
- package/dist/chunks/app-layer-YKXEQGOW.js +163 -0
- package/dist/chunks/apps-GGSIN2ZF.js +539 -0
- package/dist/chunks/chunk-3CC6MH35.js +1663 -0
- package/dist/chunks/chunk-3Y4EL3P6.js +1090 -0
- package/dist/chunks/chunk-4HYJG2JE.js +3875 -0
- package/dist/chunks/chunk-5G4E547X.js +294 -0
- package/dist/chunks/chunk-74O6FGVN.js +598 -0
- package/dist/chunks/chunk-AUJLPKGI.js +632 -0
- package/dist/chunks/chunk-BYAPLT37.js +744 -0
- package/dist/chunks/chunk-FUP7EGT7.js +1845 -0
- package/dist/chunks/chunk-I6HN3ZGW.js +88 -0
- package/dist/chunks/chunk-KRKGHVMQ.js +294 -0
- package/dist/chunks/chunk-LHYK6QO2.js +1300 -0
- package/dist/chunks/chunk-NLBKZZEK.js +224 -0
- package/dist/chunks/chunk-OG75GMVO.js +1 -0
- package/dist/chunks/chunk-Q57SVPEH.js +11663 -0
- package/dist/chunks/chunk-UACZUFIA.js +47 -0
- package/dist/chunks/chunk-VQX33YAG.js +5135 -0
- package/dist/chunks/chunk-W5GKHH77.js +168 -0
- package/dist/chunks/chunk-WYDVQOBL.js +17581 -0
- package/dist/chunks/chunk-XSAXENQD.js +4167 -0
- package/dist/chunks/chunk-Y6G6WRTG.js +210 -0
- package/dist/chunks/cli-NVKNE4AZ.js +11 -0
- package/dist/chunks/cli-X3HPLSOV.js +19 -0
- package/dist/chunks/core-commands-IM2UKLU4.js +41 -0
- package/dist/chunks/extend-YXCBULPO.js +84 -0
- package/dist/chunks/harness-GMK6IY2U.js +140 -0
- package/dist/chunks/help-LMH5FGT2.js +65 -0
- package/dist/chunks/opchat-BWXZ32GX.js +169 -0
- package/dist/chunks/organizations-4ISQRGMQ.js +738 -0
- package/dist/chunks/profile-NLDZDT3L.js +1110 -0
- package/dist/chunks/project-agent-ZPVO25HA.js +892 -0
- package/dist/chunks/sandbox-command-5GFJ2U3V.js +1652 -0
- package/dist/chunks/sandbox-template-LZN4F55Z.js +1646 -0
- package/dist/chunks/src-3RBI4DNN.js +3595 -0
- package/dist/chunks/src-EYXQS6XD.js +73427 -0
- package/dist/chunks/teams-bot-WP3FHKGA.js +137 -0
- package/dist/cli.js +77 -0
- package/dist/client-types.d.ts +222 -0
- package/dist/client.d.ts +19 -0
- package/dist/config.d.ts +184 -0
- package/dist/hosted-chat.d.ts +162 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +25481 -0
- package/dist/indicators.d.ts +38 -0
- package/dist/opchat-smoke.d.ts +47 -0
- package/dist/private-json-file.d.ts +3 -0
- package/dist/profile/local-profile-types.d.ts +160 -0
- package/dist/profile/local-profile.d.ts +69 -0
- package/dist/profile/profile-catalog.d.ts +14 -0
- package/dist/profile/profile-git.d.ts +17 -0
- package/dist/profile/profile-promotion.d.ts +21 -0
- package/dist/profile/profile-setup-gate.d.ts +22 -0
- package/dist/profile/profile-skills.d.ts +30 -0
- package/dist/profile/profile-source-upload.d.ts +29 -0
- package/dist/profile/source-upload-cache.d.ts +29 -0
- package/dist/sandbox/async-request-options.d.ts +2 -0
- package/dist/sandbox/client-handles.d.ts +113 -0
- package/dist/sandbox/client.d.ts +348 -0
- package/dist/sandbox/runtime-handle.d.ts +36 -0
- package/dist/sandbox/sandbox-instance-client.d.ts +72 -0
- package/dist/sandbox/smoke.d.ts +3 -0
- package/dist/sandbox/stream.d.ts +7 -0
- package/dist/sandbox/types/billing.d.ts +176 -0
- package/dist/sandbox/types/client.d.ts +103 -0
- package/dist/sandbox/types/core.d.ts +140 -0
- package/dist/sandbox/types/index.d.ts +14 -0
- package/dist/sandbox/types/io.d.ts +240 -0
- package/dist/sandbox/types/microsoft-teams-bot.d.ts +51 -0
- package/dist/sandbox/types/org-project-agent.d.ts +610 -0
- package/dist/sandbox/types/record.d.ts +53 -0
- package/dist/sandbox/types/responses.d.ts +295 -0
- package/dist/sandbox/types/runtime-profiles.d.ts +15 -0
- package/dist/sandbox/types/runtime.d.ts +152 -0
- package/dist/sandbox/types/schedules.d.ts +141 -0
- package/dist/sandbox/types/snapshots.d.ts +205 -0
- package/dist/sandbox/types/workflow-modes.d.ts +1 -0
- package/dist/sandbox/types/workload-sources.d.ts +20 -0
- package/dist/sandbox/url.d.ts +2 -0
- package/dist/sandbox-template/manifest.d.ts +304 -0
- package/dist/sandbox-template/manifest.js +18381 -0
- package/dist/skills/openpond-cli/SKILL.md +49 -0
- package/dist/skills/openpond-desktop-harness/SKILL.md +113 -0
- package/dist/skills/openpond-desktop-harness/agents/openai.yaml +4 -0
- package/dist/skills/openpond-taskset-authoring/SKILL.md +38 -0
- package/dist/skills/openpond-taskset-authoring/references/graders-and-rewards.md +11 -0
- package/dist/skills/openpond-taskset-authoring/references/method-selection.md +12 -0
- package/dist/skills/openpond-taskset-authoring/references/privacy-and-provenance.md +8 -0
- package/dist/skills/openpond-taskset-authoring/references/task-design.md +13 -0
- package/dist/stream.d.ts +20 -0
- package/dist/types.d.ts +57 -0
- package/dist/urls.d.ts +4 -0
- package/dist/web/assets/AppsView-Bu-Nc3Ew.js +1 -0
- package/dist/web/assets/AppsView-DIhprTIC.css +1 -0
- package/dist/web/assets/BrowserSidebar-CCguQ-DR.js +1 -0
- package/dist/web/assets/BrowserSidebar-D9i7gQzw.css +1 -0
- package/dist/web/assets/CloudWorkView-DNoPIlMj.css +1 -0
- package/dist/web/assets/CloudWorkView-Dr3xqLE5.js +1 -0
- package/dist/web/assets/CommandMenu-CnodJFHD.js +1 -0
- package/dist/web/assets/CommandMenu-hNQCm8mw.css +1 -0
- package/dist/web/assets/CommunityView-CIbjNUZr.js +1 -0
- package/dist/web/assets/CommunityView-DSik4sd2.css +1 -0
- package/dist/web/assets/GetStartedView-C3ZrnEz5.css +1 -0
- package/dist/web/assets/GetStartedView-C8nyH_PT.js +1 -0
- package/dist/web/assets/LabsExtensions-GtJzxDnK.js +1 -0
- package/dist/web/assets/LabsProfile-DG7rWYur.js +1 -0
- package/dist/web/assets/LabsRoute-D6MDHBzq.js +2 -0
- package/dist/web/assets/LabsRoute-TEt9j1vB.css +1 -0
- package/dist/web/assets/LabsSignals-BfsDN86w.js +1 -0
- package/dist/web/assets/LabsSignals-D6iA6Ryl.css +1 -0
- package/dist/web/assets/NewProjectDialog-CaKiCoVS.js +1 -0
- package/dist/web/assets/ProfileSettingsSection-DTQS2DVL.css +1 -0
- package/dist/web/assets/ProfileSettingsSection-DxEzoF3K.js +1 -0
- package/dist/web/assets/ProfileView-CEscW7Ru.js +1 -0
- package/dist/web/assets/SettingsView-DwbGu8Kj.css +1 -0
- package/dist/web/assets/SettingsView-JIRR_FO4.js +63 -0
- package/dist/web/assets/TeamChatView-B5M1ThhJ.js +3 -0
- package/dist/web/assets/TeamChatView-Bx44q3WV.css +1 -0
- package/dist/web/assets/TerminalOverlay-C1iP4OS1.js +19 -0
- package/dist/web/assets/TerminalOverlay-DwBiZLwk.css +1 -0
- package/dist/web/assets/TrainingSettingsSection-BDYvI2te.js +1 -0
- package/dist/web/assets/TrainingView-CoCFK6W3.js +5 -0
- package/dist/web/assets/WorkspaceDiffPanel-CMxKQeo7.js +14 -0
- package/dist/web/assets/WorkspaceEnvironmentMenu-B8JJbwIz.js +32 -0
- package/dist/web/assets/WorkspaceEnvironmentMenu-DGIFdG3o.css +1 -0
- package/dist/web/assets/WorkspaceGitDialogs-B4sp9BGn.js +1 -0
- package/dist/web/assets/WorkspaceMonacoEditor-BBqimG5X.js +845 -0
- package/dist/web/assets/WorkspaceMonacoEditor-BwKcXIZw.css +1 -0
- package/dist/web/assets/boxes-DAaDUabq.js +1 -0
- package/dist/web/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/web/assets/css.worker-DBVD8oXr.js +93 -0
- package/dist/web/assets/cssMode-CBF1mieJ.js +1 -0
- package/dist/web/assets/dockerfile-BG73LgW2.js +1 -0
- package/dist/web/assets/editor.worker-CdQrwHl8.js +26 -0
- package/dist/web/assets/git-commit-horizontal-CAickXpN.js +1 -0
- package/dist/web/assets/git-dialogs-BZDqS1eT.css +1 -0
- package/dist/web/assets/html.worker-CwpTb9lJ.js +470 -0
- package/dist/web/assets/htmlMode-2u8OHtWO.js +1 -0
- package/dist/web/assets/index-BdY6A-YN.css +1 -0
- package/dist/web/assets/index-CU2qhUYV.js +195 -0
- package/dist/web/assets/index-Dz2B3-4C.js +1 -0
- package/dist/web/assets/info-CQNTh78o.js +1 -0
- package/dist/web/assets/json.worker-BoL8UZqY.js +58 -0
- package/dist/web/assets/jsonMode-C4CWXViX.js +7 -0
- package/dist/web/assets/lspLanguageFeatures-CBs_BOlB.js +4 -0
- package/dist/web/assets/markdown-BFxVWTOG.js +1 -0
- package/dist/web/assets/monaco.contribution-C1LUxAKf.js +2 -0
- package/dist/web/assets/monaco.contribution-Cpf6dWsh.js +2 -0
- package/dist/web/assets/monaco.contribution-lOXpG2xQ.js +2 -0
- package/dist/web/assets/monaco.contribution-sd3cFkcN.js +2 -0
- package/dist/web/assets/monitor-DsIswrMV.js +1 -0
- package/dist/web/assets/play-CeRw21NV.js +1 -0
- package/dist/web/assets/python-CXYq3BUl.js +1 -0
- package/dist/web/assets/refresh-cw-N5b_6AP5.js +1 -0
- package/dist/web/assets/reply-BSKjZrJg.js +1 -0
- package/dist/web/assets/save-D0oHH1a3.js +1 -0
- package/dist/web/assets/settings-lists-BEg6LMcy.css +1 -0
- package/dist/web/assets/shell-DiJ1NA_G.js +1 -0
- package/dist/web/assets/toggleHighContrast-B7NOMXDY.css +1 -0
- package/dist/web/assets/toggleHighContrast-DRjlcwes.js +62 -0
- package/dist/web/assets/training-model-data-BPrc68VG.js +1 -0
- package/dist/web/assets/ts.worker-BH9nVgjN.js +67718 -0
- package/dist/web/assets/tsMode-x-1A7ZcB.js +11 -0
- package/dist/web/assets/wifi-off-Dt52ofzh.js +1 -0
- package/dist/web/assets/workers-Ciff6Dzi.js +1 -0
- package/dist/web/assets/yaml-CB67Kxnz.js +1 -0
- package/dist/web/connected-apps/github.svg +3 -0
- package/dist/web/connected-apps/google.svg +6 -0
- package/dist/web/connected-apps/linear.svg +3 -0
- package/dist/web/connected-apps/microsoft.svg +6 -0
- package/dist/web/connected-apps/notion.svg +3 -0
- package/dist/web/connected-apps/openpond-mcp.svg +4 -0
- package/dist/web/connected-apps/slack.svg +6 -0
- package/dist/web/connected-apps/x.svg +3 -0
- package/dist/web/favicon-16x16.png +0 -0
- package/dist/web/favicon-32x32.png +0 -0
- package/dist/web/index.html +16 -0
- package/dist/web/openpond-icon.png +0 -0
- package/dist/web/openpond-wordlogo-white.png +0 -0
- package/dist/web/tokens/eth.svg +1 -0
- package/dist/web/tokens/usdc.svg +1 -0
- package/docs/agent-edit-check-status-json.md +132 -0
- package/docs/command-reference.md +695 -0
- package/package.json +84 -0
|
@@ -0,0 +1,1845 @@
|
|
|
1
|
+
import { createRequire as __openpondCreateRequire } from "node:module"; var require = __openpondCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
SOURCE_UPLOAD_CACHE_PATH,
|
|
4
|
+
materializeSourceUploadFile,
|
|
5
|
+
readSourceUploadCache,
|
|
6
|
+
writeSourceUploadCache
|
|
7
|
+
} from "./chunk-I6HN3ZGW.js";
|
|
8
|
+
import {
|
|
9
|
+
yaml
|
|
10
|
+
} from "./chunk-WYDVQOBL.js";
|
|
11
|
+
import {
|
|
12
|
+
loadGlobalConfig,
|
|
13
|
+
saveGlobalConfig
|
|
14
|
+
} from "./chunk-XSAXENQD.js";
|
|
15
|
+
|
|
16
|
+
// ../../packages/cloud/src/profile/profile-setup-gate.ts
|
|
17
|
+
var OpenPondProfileSetupRequiredError = class extends Error {
|
|
18
|
+
code = "agent_source_setup_required";
|
|
19
|
+
details;
|
|
20
|
+
constructor(actionId, setupGate) {
|
|
21
|
+
const labels = setupGate.blockingRequirements.map((requirement) => requirement.label);
|
|
22
|
+
super(
|
|
23
|
+
[
|
|
24
|
+
"agent_source_setup_required",
|
|
25
|
+
`Profile action ${actionId} has unresolved required setup.`,
|
|
26
|
+
labels.length ? `Missing setup: ${labels.join(", ")}` : ""
|
|
27
|
+
].filter(Boolean).join("\n")
|
|
28
|
+
);
|
|
29
|
+
this.name = "OpenPondProfileSetupRequiredError";
|
|
30
|
+
this.details = {
|
|
31
|
+
error: "agent_source_setup_required",
|
|
32
|
+
actionId,
|
|
33
|
+
setupGate,
|
|
34
|
+
missing: labels,
|
|
35
|
+
blockingSetupRequirements: setupGate.blockingRequirements,
|
|
36
|
+
setupRequirements: setupGate.requirements
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
function buildOpenPondProfileSetupGate(input) {
|
|
41
|
+
const requirements = /* @__PURE__ */ new Map();
|
|
42
|
+
for (const record2 of input.sourceSetupRequirements ?? []) {
|
|
43
|
+
const sourceActionId = setupRequirementActionId(record2);
|
|
44
|
+
if (input.actionId && sourceActionId && sourceActionId !== input.actionId && text(record2.actionName) !== input.actionId && text(record2.sourceActionId) !== input.actionId) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const requirement = setupRequirementFromRecord({
|
|
48
|
+
record: record2,
|
|
49
|
+
source: "source_upload_metadata",
|
|
50
|
+
actionId: sourceActionId
|
|
51
|
+
});
|
|
52
|
+
if (requirement) requirements.set(requirement.ref, requirement);
|
|
53
|
+
}
|
|
54
|
+
for (const action of input.actionCatalog) {
|
|
55
|
+
if (input.actionId && action.id !== input.actionId && action.name !== input.actionId) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (const record2 of action.setupRequirements ?? []) {
|
|
59
|
+
const requirement = setupRequirementFromRecord({
|
|
60
|
+
record: record2,
|
|
61
|
+
source: "action_catalog",
|
|
62
|
+
actionId: action.id
|
|
63
|
+
});
|
|
64
|
+
if (requirement) requirements.set(requirement.ref, requirement);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const allRequirements = Array.from(requirements.values());
|
|
68
|
+
const blockingRequirements = allRequirements.filter((requirement) => requirement.blocking);
|
|
69
|
+
const optionalMissingCount = allRequirements.filter(
|
|
70
|
+
(requirement) => !requirement.required && !isReadyStatus(requirement.status)
|
|
71
|
+
).length;
|
|
72
|
+
const readyCount = allRequirements.filter((requirement) => isReadyStatus(requirement.status)).length;
|
|
73
|
+
return {
|
|
74
|
+
status: gateStatus(blockingRequirements),
|
|
75
|
+
requirementCount: allRequirements.length,
|
|
76
|
+
blockingCount: blockingRequirements.length,
|
|
77
|
+
optionalMissingCount,
|
|
78
|
+
readyCount,
|
|
79
|
+
requirements: allRequirements,
|
|
80
|
+
blockingRequirements
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function assertOpenPondProfileActionReady(actionId, setupGate) {
|
|
84
|
+
if (setupGate.blockingRequirements.length > 0) {
|
|
85
|
+
throw new OpenPondProfileSetupRequiredError(actionId, setupGate);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function formatOpenPondProfileSetupRequirement(requirement) {
|
|
89
|
+
const prefix = requirement.actionId ? `${requirement.actionId}: ` : "";
|
|
90
|
+
const required = requirement.required ? "required" : "optional";
|
|
91
|
+
return `${prefix}${requirement.label} (${required}, ${requirement.status})`;
|
|
92
|
+
}
|
|
93
|
+
function setupRequirementFromRecord(input) {
|
|
94
|
+
const source = input.source === "action_catalog" && text(input.record.source) === "source_upload_metadata" ? "source_upload_metadata" : input.source;
|
|
95
|
+
const kind = text(input.record.kind) ?? text(input.record.type);
|
|
96
|
+
const label = text(input.record.label) ?? text(input.record.name) ?? text(input.record.key) ?? text(input.record.provider) ?? text(input.record.tool) ?? text(input.record.command) ?? text(input.record.packageName) ?? text(input.record.path) ?? text(input.record.repo) ?? text(input.record.url) ?? text(input.record.id) ?? kind ?? "setup requirement";
|
|
97
|
+
const required = input.record.required !== false;
|
|
98
|
+
const status = setupStatus(input.record);
|
|
99
|
+
const ref = [
|
|
100
|
+
source,
|
|
101
|
+
input.actionId ?? "source",
|
|
102
|
+
kind ?? "requirement",
|
|
103
|
+
label
|
|
104
|
+
].join(":");
|
|
105
|
+
return {
|
|
106
|
+
ref,
|
|
107
|
+
source,
|
|
108
|
+
actionId: input.actionId,
|
|
109
|
+
kind,
|
|
110
|
+
label,
|
|
111
|
+
status,
|
|
112
|
+
required,
|
|
113
|
+
blocking: required && !isReadyStatus(status)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function setupRequirementActionId(record2) {
|
|
117
|
+
return text(record2.actionId) ?? text(record2.sourceActionId) ?? text(record2.actionName);
|
|
118
|
+
}
|
|
119
|
+
function setupStatus(record2) {
|
|
120
|
+
const explicit = text(record2.status) ?? text(record2.state);
|
|
121
|
+
if (explicit) {
|
|
122
|
+
if (explicit === "satisfied" || explicit === "connected" || explicit === "provided") {
|
|
123
|
+
return "ready";
|
|
124
|
+
}
|
|
125
|
+
return explicit;
|
|
126
|
+
}
|
|
127
|
+
if (typeof record2.satisfied === "boolean") {
|
|
128
|
+
return record2.satisfied ? "ready" : "setup_required";
|
|
129
|
+
}
|
|
130
|
+
if (typeof record2.ready === "boolean") {
|
|
131
|
+
return record2.ready ? "ready" : "setup_required";
|
|
132
|
+
}
|
|
133
|
+
return "setup_required";
|
|
134
|
+
}
|
|
135
|
+
function gateStatus(blockingRequirements) {
|
|
136
|
+
if (blockingRequirements.length === 0) return "ready";
|
|
137
|
+
return blockingRequirements.some(
|
|
138
|
+
(requirement) => ["blocked", "unsupported", "unsupported_dependency", "stale"].includes(requirement.status)
|
|
139
|
+
) ? "blocked" : "setup_required";
|
|
140
|
+
}
|
|
141
|
+
function isReadyStatus(status) {
|
|
142
|
+
return status === "ready" || status === "disabled";
|
|
143
|
+
}
|
|
144
|
+
function text(value) {
|
|
145
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ../../packages/cloud/src/profile/profile-source-upload.ts
|
|
149
|
+
import { stat } from "node:fs/promises";
|
|
150
|
+
import path2 from "node:path";
|
|
151
|
+
|
|
152
|
+
// ../../packages/cloud/src/profile/profile-git.ts
|
|
153
|
+
import { spawn } from "node:child_process";
|
|
154
|
+
import { existsSync } from "node:fs";
|
|
155
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
156
|
+
import path from "node:path";
|
|
157
|
+
var PROFILE_REPO_GITIGNORE = [
|
|
158
|
+
"**/node_modules/",
|
|
159
|
+
"**/.bun/",
|
|
160
|
+
"**/.env",
|
|
161
|
+
"**/.env.*",
|
|
162
|
+
"**/.openpond/goals/",
|
|
163
|
+
"**/.openpond/traces/",
|
|
164
|
+
"**/.openpond/vendor/",
|
|
165
|
+
"**/.openpond/eval-results.json",
|
|
166
|
+
"**/.openpond/artifact-index.json",
|
|
167
|
+
"**/.openpond/source-upload-cache.json",
|
|
168
|
+
""
|
|
169
|
+
].join("\n");
|
|
170
|
+
async function ensureProfileGitRepo(repoPath) {
|
|
171
|
+
if (!existsSync(path.join(repoPath, ".git"))) {
|
|
172
|
+
const init = await runGitCommand(repoPath, ["init", "-b", "main"]);
|
|
173
|
+
if (init.code !== 0) {
|
|
174
|
+
throw new Error(gitFailureMessage("git init", init));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const gitignorePath = path.join(repoPath, ".gitignore");
|
|
178
|
+
if (!existsSync(gitignorePath)) {
|
|
179
|
+
await writeFile(gitignorePath, PROFILE_REPO_GITIGNORE, "utf8");
|
|
180
|
+
} else {
|
|
181
|
+
await ensureProfileGitignoreRules(gitignorePath);
|
|
182
|
+
}
|
|
183
|
+
if (await hasGitHead(repoPath)) return;
|
|
184
|
+
const state = await loadProfileGitState(repoPath);
|
|
185
|
+
if (!state.dirty && state.files.length === 0) return;
|
|
186
|
+
const add = await runGitCommand(repoPath, ["add", "-A"]);
|
|
187
|
+
if (add.code !== 0) {
|
|
188
|
+
throw new Error(gitFailureMessage("git add", add));
|
|
189
|
+
}
|
|
190
|
+
const commit = await runGitCommand(repoPath, ["commit", "-m", "Initialize OpenPond profile"], {
|
|
191
|
+
env: {
|
|
192
|
+
...process.env,
|
|
193
|
+
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME || "OpenPond",
|
|
194
|
+
GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL || "openpond@example.invalid",
|
|
195
|
+
GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME || "OpenPond",
|
|
196
|
+
GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL || "openpond@example.invalid"
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
|
|
200
|
+
throw new Error(gitFailureMessage("git commit", commit));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function ensureProfileGitignoreRules(gitignorePath) {
|
|
204
|
+
const current = await readFile(gitignorePath, "utf8").catch(() => "");
|
|
205
|
+
const missing = PROFILE_REPO_GITIGNORE.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).filter((line) => !current.split(/\r?\n/).some((existing) => existing.trim() === line));
|
|
206
|
+
if (missing.length === 0) return;
|
|
207
|
+
const suffix = current.endsWith("\n") || current.length === 0 ? "" : "\n";
|
|
208
|
+
await writeFile(gitignorePath, `${current}${suffix}${missing.join("\n")}
|
|
209
|
+
`, "utf8");
|
|
210
|
+
}
|
|
211
|
+
async function loadProfileGitState(repoPath) {
|
|
212
|
+
if (!existsSync(path.join(repoPath, ".git"))) {
|
|
213
|
+
return {
|
|
214
|
+
isRepo: false,
|
|
215
|
+
branch: null,
|
|
216
|
+
head: null,
|
|
217
|
+
shortHead: null,
|
|
218
|
+
dirty: false,
|
|
219
|
+
upstream: null,
|
|
220
|
+
ahead: null,
|
|
221
|
+
behind: null,
|
|
222
|
+
remoteUrl: null,
|
|
223
|
+
files: [],
|
|
224
|
+
error: null
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const inside = await runGitCommand(repoPath, ["rev-parse", "--is-inside-work-tree"]);
|
|
228
|
+
if (inside.code !== 0 || inside.stdout.trim() !== "true") {
|
|
229
|
+
return {
|
|
230
|
+
isRepo: false,
|
|
231
|
+
branch: null,
|
|
232
|
+
head: null,
|
|
233
|
+
shortHead: null,
|
|
234
|
+
dirty: false,
|
|
235
|
+
upstream: null,
|
|
236
|
+
ahead: null,
|
|
237
|
+
behind: null,
|
|
238
|
+
remoteUrl: null,
|
|
239
|
+
files: [],
|
|
240
|
+
error: inside.stderr.trim() || inside.stdout.trim() || "not a Git work tree"
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const [branch, head, status, upstream, remoteUrl] = await Promise.all([
|
|
244
|
+
gitText(repoPath, ["branch", "--show-current"]),
|
|
245
|
+
gitText(repoPath, ["rev-parse", "HEAD"]),
|
|
246
|
+
runGitCommand(repoPath, ["status", "--porcelain=v1", "-uall"]),
|
|
247
|
+
gitText(repoPath, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]),
|
|
248
|
+
gitText(repoPath, ["remote", "get-url", "origin"])
|
|
249
|
+
]);
|
|
250
|
+
const files = status.code === 0 ? parsePorcelainStatus(status.stdout) : [];
|
|
251
|
+
const aheadBehind = upstream ? await loadAheadBehind(repoPath) : { ahead: null, behind: null };
|
|
252
|
+
const resolvedHead = head || null;
|
|
253
|
+
return {
|
|
254
|
+
isRepo: true,
|
|
255
|
+
branch: branch || null,
|
|
256
|
+
head: resolvedHead,
|
|
257
|
+
shortHead: resolvedHead ? resolvedHead.slice(0, 12) : null,
|
|
258
|
+
dirty: files.length > 0,
|
|
259
|
+
upstream,
|
|
260
|
+
ahead: aheadBehind.ahead,
|
|
261
|
+
behind: aheadBehind.behind,
|
|
262
|
+
remoteUrl,
|
|
263
|
+
files,
|
|
264
|
+
error: status.code === 0 ? null : gitFailureMessage("git status", status)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
async function commitProfileChanges(repoPath, message) {
|
|
268
|
+
const before = await loadProfileGitState(repoPath);
|
|
269
|
+
if (!before.isRepo) {
|
|
270
|
+
throw new Error(before.error ?? "Active OpenPond profile source is not a Git repo.");
|
|
271
|
+
}
|
|
272
|
+
if (!before.dirty) {
|
|
273
|
+
return {
|
|
274
|
+
committed: false,
|
|
275
|
+
stdout: "",
|
|
276
|
+
stderr: "",
|
|
277
|
+
state: before
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const add = await runGitCommand(repoPath, ["add", "-A"]);
|
|
281
|
+
if (add.code !== 0) {
|
|
282
|
+
throw new Error(gitFailureMessage("git add", add));
|
|
283
|
+
}
|
|
284
|
+
const commit = await runGitCommand(repoPath, ["commit", "-m", message]);
|
|
285
|
+
if (commit.code !== 0) {
|
|
286
|
+
throw new Error(gitFailureMessage("git commit", commit));
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
committed: true,
|
|
290
|
+
stdout: commit.stdout,
|
|
291
|
+
stderr: commit.stderr,
|
|
292
|
+
state: await loadProfileGitState(repoPath)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
async function profileGitHead(repoPath) {
|
|
296
|
+
return gitText(repoPath, ["rev-parse", "HEAD"]);
|
|
297
|
+
}
|
|
298
|
+
async function hasGitHead(repoPath) {
|
|
299
|
+
const result = await runGitCommand(repoPath, ["rev-parse", "--verify", "HEAD"]);
|
|
300
|
+
return result.code === 0;
|
|
301
|
+
}
|
|
302
|
+
async function loadAheadBehind(repoPath) {
|
|
303
|
+
const result = await runGitCommand(repoPath, ["rev-list", "--left-right", "--count", "@{u}...HEAD"]);
|
|
304
|
+
if (result.code !== 0) return { ahead: null, behind: null };
|
|
305
|
+
const [behindRaw, aheadRaw] = result.stdout.trim().split(/\s+/);
|
|
306
|
+
const behind = behindRaw ? Number.parseInt(behindRaw, 10) : Number.NaN;
|
|
307
|
+
const ahead = aheadRaw ? Number.parseInt(aheadRaw, 10) : Number.NaN;
|
|
308
|
+
return {
|
|
309
|
+
ahead: Number.isFinite(ahead) ? ahead : null,
|
|
310
|
+
behind: Number.isFinite(behind) ? behind : null
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
async function gitText(repoPath, args) {
|
|
314
|
+
const result = await runGitCommand(repoPath, args);
|
|
315
|
+
if (result.code !== 0) return null;
|
|
316
|
+
const text5 = result.stdout.trim();
|
|
317
|
+
return text5 || null;
|
|
318
|
+
}
|
|
319
|
+
function parsePorcelainStatus(output) {
|
|
320
|
+
return output.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean).map((line) => {
|
|
321
|
+
const rawIndex = line[0] ?? " ";
|
|
322
|
+
const rawWorktree = line[1] ?? " ";
|
|
323
|
+
const status = `${rawIndex}${rawWorktree}`.trim() || "changed";
|
|
324
|
+
const rawPath = line.slice(3);
|
|
325
|
+
const renameParts = rawPath.split(" -> ");
|
|
326
|
+
const indexStatus = rawIndex === " " ? null : rawIndex;
|
|
327
|
+
const worktreeStatus = rawWorktree === " " ? null : rawWorktree;
|
|
328
|
+
const pathValue = renameParts.length === 2 ? renameParts[1] : rawPath;
|
|
329
|
+
const originalPath = renameParts.length === 2 ? renameParts[0] : null;
|
|
330
|
+
return {
|
|
331
|
+
path: unquoteGitPath(pathValue),
|
|
332
|
+
originalPath: originalPath ? unquoteGitPath(originalPath) : null,
|
|
333
|
+
indexStatus,
|
|
334
|
+
worktreeStatus,
|
|
335
|
+
status,
|
|
336
|
+
category: gitChangeCategory(indexStatus, worktreeStatus)
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function gitChangeCategory(indexStatus, worktreeStatus) {
|
|
341
|
+
if (indexStatus === "?" && worktreeStatus === "?") return "untracked";
|
|
342
|
+
if (indexStatus === "R" || worktreeStatus === "R") return "renamed";
|
|
343
|
+
if (indexStatus === "A" || worktreeStatus === "A") return "added";
|
|
344
|
+
if (indexStatus === "D" || worktreeStatus === "D") return "deleted";
|
|
345
|
+
if (indexStatus === "M" || worktreeStatus === "M") return "modified";
|
|
346
|
+
return "changed";
|
|
347
|
+
}
|
|
348
|
+
function unquoteGitPath(value) {
|
|
349
|
+
const trimmed = value.trim();
|
|
350
|
+
if (!trimmed.startsWith('"') || !trimmed.endsWith('"')) return trimmed;
|
|
351
|
+
try {
|
|
352
|
+
return JSON.parse(trimmed);
|
|
353
|
+
} catch {
|
|
354
|
+
return trimmed.slice(1, -1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function gitFailureMessage(label, result) {
|
|
358
|
+
const detail = result.stderr.trim() || result.stdout.trim();
|
|
359
|
+
return detail || `${label} failed with exit code ${result.code ?? "unknown"}`;
|
|
360
|
+
}
|
|
361
|
+
async function runGitCommand(repoPath, args, options = {}) {
|
|
362
|
+
return new Promise((resolve, reject) => {
|
|
363
|
+
const proc = spawn("git", args, {
|
|
364
|
+
cwd: repoPath,
|
|
365
|
+
env: options.env,
|
|
366
|
+
stdio: "pipe"
|
|
367
|
+
});
|
|
368
|
+
let stdout = "";
|
|
369
|
+
let stderr = "";
|
|
370
|
+
proc.stdout?.on("data", (chunk) => {
|
|
371
|
+
stdout += String(chunk);
|
|
372
|
+
});
|
|
373
|
+
proc.stderr?.on("data", (chunk) => {
|
|
374
|
+
stderr += String(chunk);
|
|
375
|
+
});
|
|
376
|
+
proc.on("error", reject);
|
|
377
|
+
proc.on("close", (code) => {
|
|
378
|
+
resolve({ code, stdout, stderr });
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ../../packages/cloud/src/profile/profile-source-upload.ts
|
|
384
|
+
var PROFILE_SOURCE_UPLOAD_MAX_FILES = 1500;
|
|
385
|
+
var PROFILE_SOURCE_UPLOAD_MAX_FILE_BYTES = 8 * 1024 * 1024;
|
|
386
|
+
var PROFILE_SOURCE_UPLOAD_MAX_BYTES = 100 * 1024 * 1024;
|
|
387
|
+
var PROFILE_SOURCE_UPLOAD_CONCURRENCY = 8;
|
|
388
|
+
var PROFILE_SOURCE_UPLOAD_LIMITS = {
|
|
389
|
+
maxFiles: PROFILE_SOURCE_UPLOAD_MAX_FILES,
|
|
390
|
+
maxFileBytes: PROFILE_SOURCE_UPLOAD_MAX_FILE_BYTES,
|
|
391
|
+
maxTotalBytes: PROFILE_SOURCE_UPLOAD_MAX_BYTES,
|
|
392
|
+
concurrency: PROFILE_SOURCE_UPLOAD_CONCURRENCY
|
|
393
|
+
};
|
|
394
|
+
var PROFILE_SOURCE_UPLOAD_TRANSPORT = {
|
|
395
|
+
mode: "single_json_payload",
|
|
396
|
+
chunkingSupported: false
|
|
397
|
+
};
|
|
398
|
+
async function collectProfileSourceUploadEntries(repoPath) {
|
|
399
|
+
const files = await runGitCommand(repoPath, ["ls-files", "-z"]);
|
|
400
|
+
if (files.code !== 0) {
|
|
401
|
+
throw new Error(files.stderr.trim() || files.stdout.trim() || "git ls-files failed for profile source upload");
|
|
402
|
+
}
|
|
403
|
+
const sourcePaths = files.stdout.split("\0").map((filePath) => filePath.trim()).filter(Boolean).filter((filePath) => {
|
|
404
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
405
|
+
return isSafeProfileSourcePath(normalized) && !shouldSkipProfileSourcePath(normalized);
|
|
406
|
+
}).sort();
|
|
407
|
+
if (sourcePaths.length === 0) {
|
|
408
|
+
throw new Error("No committed profile source files found to upload.");
|
|
409
|
+
}
|
|
410
|
+
if (sourcePaths.length > PROFILE_SOURCE_UPLOAD_MAX_FILES) {
|
|
411
|
+
throw new Error(`Too many profile source files to upload: ${sourcePaths.length} > ${PROFILE_SOURCE_UPLOAD_MAX_FILES}`);
|
|
412
|
+
}
|
|
413
|
+
const filesToUpload = (await mapWithConcurrency(sourcePaths, PROFILE_SOURCE_UPLOAD_CONCURRENCY, async (sourcePath) => {
|
|
414
|
+
const absolutePath = path2.resolve(repoPath, sourcePath);
|
|
415
|
+
const relative = path2.relative(repoPath, absolutePath).replace(/\\/g, "/");
|
|
416
|
+
if (!isSafeProfileSourcePath(relative)) {
|
|
417
|
+
throw new Error(`Profile source path escapes repo: ${sourcePath}`);
|
|
418
|
+
}
|
|
419
|
+
const fileStat = await stat(absolutePath);
|
|
420
|
+
if (!fileStat.isFile()) return null;
|
|
421
|
+
if (fileStat.size > PROFILE_SOURCE_UPLOAD_MAX_FILE_BYTES) {
|
|
422
|
+
throw new Error(`Profile source file is too large: ${sourcePath} (${fileStat.size} bytes)`);
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
path: relative,
|
|
426
|
+
absolutePath,
|
|
427
|
+
size: fileStat.size,
|
|
428
|
+
mtimeMs: Math.trunc(fileStat.mtimeMs),
|
|
429
|
+
ctimeMs: Math.trunc(fileStat.ctimeMs)
|
|
430
|
+
};
|
|
431
|
+
})).filter((file) => file !== null);
|
|
432
|
+
let totalBytes = 0;
|
|
433
|
+
for (const file of filesToUpload) {
|
|
434
|
+
totalBytes += file.size;
|
|
435
|
+
if (totalBytes > PROFILE_SOURCE_UPLOAD_MAX_BYTES) {
|
|
436
|
+
throw new Error(`Profile source upload is too large: ${totalBytes} > ${PROFILE_SOURCE_UPLOAD_MAX_BYTES}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const cache = await readSourceUploadCache(repoPath);
|
|
440
|
+
const materialized = await mapWithConcurrency(
|
|
441
|
+
filesToUpload,
|
|
442
|
+
PROFILE_SOURCE_UPLOAD_CONCURRENCY,
|
|
443
|
+
async (file) => materializeSourceUploadFile(file, cache)
|
|
444
|
+
);
|
|
445
|
+
await writeSourceUploadCache(repoPath, materialized).catch(() => {
|
|
446
|
+
});
|
|
447
|
+
const entries = materialized.map((item) => item.entry);
|
|
448
|
+
return {
|
|
449
|
+
entries,
|
|
450
|
+
fileCount: entries.length,
|
|
451
|
+
totalBytes,
|
|
452
|
+
limits: PROFILE_SOURCE_UPLOAD_LIMITS,
|
|
453
|
+
transport: PROFILE_SOURCE_UPLOAD_TRANSPORT
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
457
|
+
const results = new Array(items.length);
|
|
458
|
+
let nextIndex = 0;
|
|
459
|
+
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
460
|
+
await Promise.all(
|
|
461
|
+
Array.from({ length: workerCount }, async () => {
|
|
462
|
+
while (nextIndex < items.length) {
|
|
463
|
+
const index = nextIndex;
|
|
464
|
+
nextIndex += 1;
|
|
465
|
+
results[index] = await mapper(items[index], index);
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
);
|
|
469
|
+
return results;
|
|
470
|
+
}
|
|
471
|
+
function isSafeProfileSourcePath(filePath) {
|
|
472
|
+
return filePath.length > 0 && !filePath.includes("\0") && !path2.isAbsolute(filePath) && !filePath.split(/[\\/]+/).some((part) => !part || part === "." || part === "..");
|
|
473
|
+
}
|
|
474
|
+
function shouldSkipProfileSourcePath(filePath) {
|
|
475
|
+
return filePath.split(/[\\/]+/).some((segment, index, segments) => {
|
|
476
|
+
const lower = segment.toLowerCase();
|
|
477
|
+
if (lower === ".git" || lower === "node_modules" || lower === ".bun") return true;
|
|
478
|
+
if (lower === ".env" || lower.startsWith(".env.")) return true;
|
|
479
|
+
if (filePath.replace(/\\/g, "/") === SOURCE_UPLOAD_CACHE_PATH) return true;
|
|
480
|
+
if (lower !== ".openpond") return false;
|
|
481
|
+
const nested = segments[index + 1]?.toLowerCase();
|
|
482
|
+
return nested === "goals" || nested === "traces" || nested === "vendor" || nested === "eval-results.json" || nested === "artifact-index.json";
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ../../packages/cloud/src/profile/profile-promotion.ts
|
|
487
|
+
function hostedSourceCheckStatusFromPayload(input) {
|
|
488
|
+
const checkResult = record(input.checkResult);
|
|
489
|
+
const deployPlan = record(checkResult?.deployPlan) ?? record(input.deployPlan);
|
|
490
|
+
const source = record(deployPlan?.source);
|
|
491
|
+
const checks = record(deployPlan?.checks);
|
|
492
|
+
const workItem = record(checkResult?.workItem);
|
|
493
|
+
const sourceCheckStatus = record(checkResult?.sourceCheckStatus);
|
|
494
|
+
return {
|
|
495
|
+
status: input.status,
|
|
496
|
+
agentId: input.agentId,
|
|
497
|
+
workItemId: text2(workItem?.id),
|
|
498
|
+
deployPlanStatus: text2(deployPlan?.status),
|
|
499
|
+
canRun: booleanValue(deployPlan?.canRun),
|
|
500
|
+
canDeploy: booleanValue(deployPlan?.canDeploy),
|
|
501
|
+
sourceRef: text2(source?.sourceRef),
|
|
502
|
+
sourceCommitSha: text2(source?.sourceCommitSha),
|
|
503
|
+
manifestHash: text2(source?.manifestHash),
|
|
504
|
+
manifestPath: text2(source?.manifestPath),
|
|
505
|
+
setupCommands: textArray(checks?.setupCommands),
|
|
506
|
+
validationCommands: textArray(checks?.validationCommands),
|
|
507
|
+
requiredChecks: textArray(checks?.requiredChecks),
|
|
508
|
+
evalNames: textArray(checks?.evalNames),
|
|
509
|
+
blockedReasons: textArray(deployPlan?.blockedReasons),
|
|
510
|
+
staleReasons: textArray(deployPlan?.staleReasons),
|
|
511
|
+
runtimeId: text2(sourceCheckStatus?.latestRuntimeId),
|
|
512
|
+
sandboxId: text2(sourceCheckStatus?.latestSandboxId),
|
|
513
|
+
traceArtifactRefs: textArray(sourceCheckStatus?.traceArtifactRefs),
|
|
514
|
+
evalResultArtifactRefs: textArray(sourceCheckStatus?.evalResultArtifactRefs),
|
|
515
|
+
validatorArtifactRefs: textArray(sourceCheckStatus?.validatorArtifactRefs),
|
|
516
|
+
checkedAt: input.checkedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
517
|
+
error: input.error ?? null
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function hostedPublishStatusFromPayload(input) {
|
|
521
|
+
const result = record(input.publishResult);
|
|
522
|
+
const snapshot = record(result?.activeManifestSnapshot) ?? record(result?.manifestSnapshot) ?? record(result?.snapshot);
|
|
523
|
+
return {
|
|
524
|
+
status: input.status ?? "published",
|
|
525
|
+
agentId: input.agentId,
|
|
526
|
+
snapshotId: text2(snapshot?.id),
|
|
527
|
+
sourceRef: text2(snapshot?.sourceRef),
|
|
528
|
+
sourceCommitSha: text2(snapshot?.sourceCommitSha),
|
|
529
|
+
manifestHash: text2(snapshot?.manifestHash),
|
|
530
|
+
manifestPath: text2(snapshot?.manifestPath),
|
|
531
|
+
buildStatus: text2(snapshot?.buildStatus),
|
|
532
|
+
validationStatus: text2(snapshot?.validationStatus),
|
|
533
|
+
evalStatus: text2(snapshot?.evalStatus),
|
|
534
|
+
publishedAt: text2(result?.publishedAt) ?? text2(snapshot?.publishedAt),
|
|
535
|
+
error: input.error ?? null
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function hostedRunSummaryFromPayload(input) {
|
|
539
|
+
const result = record(input.runResult);
|
|
540
|
+
const run = record(result?.run);
|
|
541
|
+
const runtimeSource = record(run?.runtimeSource);
|
|
542
|
+
const metadata = record(run?.metadata);
|
|
543
|
+
const sourceSummary = record(metadata?.sourceSummary);
|
|
544
|
+
const setupGate = record(metadata?.setupGate);
|
|
545
|
+
const responseSummary = record(metadata?.responseSummary);
|
|
546
|
+
const traceSummary = record(metadata?.traceSummary);
|
|
547
|
+
const evalSummary = record(metadata?.evalSummary);
|
|
548
|
+
return {
|
|
549
|
+
status: text2(run?.status) ?? (input.error ? "failed" : "running"),
|
|
550
|
+
agentId: text2(run?.agentId) ?? input.agentId,
|
|
551
|
+
runId: text2(run?.id),
|
|
552
|
+
runtimeId: text2(run?.runtimeId),
|
|
553
|
+
sandboxId: text2(run?.sandboxId),
|
|
554
|
+
sourceRef: text2(runtimeSource?.sourceRef) ?? text2(sourceSummary?.sourceRef) ?? text2(metadata?.sourceRef),
|
|
555
|
+
sourceCommitSha: text2(runtimeSource?.sourceCommitSha) ?? text2(sourceSummary?.sourceCommitSha) ?? text2(metadata?.hostedHead),
|
|
556
|
+
manifestHash: text2(runtimeSource?.manifestHash) ?? text2(sourceSummary?.manifestHash),
|
|
557
|
+
setupGateStatus: text2(setupGate?.status),
|
|
558
|
+
setupRequirementRefs: setupRequirementRefs(setupGate),
|
|
559
|
+
artifactRefs: textArray(responseSummary?.artifactRefs),
|
|
560
|
+
traceArtifactRefs: textArray(traceSummary?.artifactRefs),
|
|
561
|
+
evalArtifactRefs: textArray(evalSummary?.artifactRefs),
|
|
562
|
+
startedAt: text2(run?.createdAt),
|
|
563
|
+
completedAt: text2(run?.completedAt),
|
|
564
|
+
error: input.error ?? null
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function hostedRunStatusFromRunSummary(summary) {
|
|
568
|
+
if (!summary?.status) return "not_started";
|
|
569
|
+
if (summary.status === "succeeded" || summary.status === "passed") return "passed";
|
|
570
|
+
if (summary.status === "failed" || summary.status === "cancelled") return "failed";
|
|
571
|
+
return "running";
|
|
572
|
+
}
|
|
573
|
+
function setupRequirementRefs(setupGate) {
|
|
574
|
+
if (!setupGate) return [];
|
|
575
|
+
const requirements = Array.isArray(setupGate.requirements) ? setupGate.requirements : Array.isArray(setupGate.blockingRequirements) ? setupGate.blockingRequirements : [];
|
|
576
|
+
return requirements.map((item) => {
|
|
577
|
+
const requirement = record(item);
|
|
578
|
+
return text2(requirement?.ref) ?? text2(requirement?.id) ?? text2(requirement?.label);
|
|
579
|
+
}).filter((item) => Boolean(item));
|
|
580
|
+
}
|
|
581
|
+
function record(value) {
|
|
582
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
583
|
+
}
|
|
584
|
+
function text2(value) {
|
|
585
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
586
|
+
}
|
|
587
|
+
function booleanValue(value) {
|
|
588
|
+
return typeof value === "boolean" ? value : null;
|
|
589
|
+
}
|
|
590
|
+
function textArray(value) {
|
|
591
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ../../packages/cloud/src/profile/local-profile.ts
|
|
595
|
+
import { createRequire } from "node:module";
|
|
596
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
597
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
598
|
+
import { cp, mkdir, readFile as readFile4, readdir as readdir2, symlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
599
|
+
import os from "node:os";
|
|
600
|
+
import path5 from "node:path";
|
|
601
|
+
|
|
602
|
+
// ../../packages/cloud/src/profile/local-profile-types.ts
|
|
603
|
+
function emptyProfileCatalogState(error = null) {
|
|
604
|
+
return {
|
|
605
|
+
actionCount: 0,
|
|
606
|
+
generatedAt: null,
|
|
607
|
+
manifestPath: null,
|
|
608
|
+
registryPath: null,
|
|
609
|
+
stale: true,
|
|
610
|
+
error
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function emptyProfileSkillCatalogState(error = null) {
|
|
614
|
+
return {
|
|
615
|
+
skillCount: 0,
|
|
616
|
+
generatedAt: null,
|
|
617
|
+
stale: true,
|
|
618
|
+
error
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function emptyProfileSetupGate() {
|
|
622
|
+
return {
|
|
623
|
+
status: "ready",
|
|
624
|
+
requirementCount: 0,
|
|
625
|
+
blockingCount: 0,
|
|
626
|
+
optionalMissingCount: 0,
|
|
627
|
+
readyCount: 0,
|
|
628
|
+
requirements: [],
|
|
629
|
+
blockingRequirements: []
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function emptyProfileDiffSummary() {
|
|
633
|
+
return {
|
|
634
|
+
changedAgents: [],
|
|
635
|
+
newAgents: [],
|
|
636
|
+
deletedAgents: [],
|
|
637
|
+
changedSkills: [],
|
|
638
|
+
changedActions: [],
|
|
639
|
+
changedExtensions: [],
|
|
640
|
+
setupChanges: [],
|
|
641
|
+
envRequirementChanges: [],
|
|
642
|
+
files: []
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function emptyProfileSummary(error = null) {
|
|
646
|
+
return {
|
|
647
|
+
state: error ? "error" : "none",
|
|
648
|
+
message: error ?? "No active OpenPond profile.",
|
|
649
|
+
agentCount: 0,
|
|
650
|
+
actionCount: 0,
|
|
651
|
+
defaultAction: null,
|
|
652
|
+
checkFresh: false,
|
|
653
|
+
checkStaleReason: error ?? "No active profile.",
|
|
654
|
+
localHead: null,
|
|
655
|
+
hostedHead: null
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ../../packages/cloud/src/profile/profile-catalog.ts
|
|
660
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
661
|
+
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
662
|
+
import path3 from "node:path";
|
|
663
|
+
var AGENT_MANIFEST_PATH = ".openpond/agent-manifest.json";
|
|
664
|
+
var ACTION_REGISTRY_PATH = ".openpond/action-registry.json";
|
|
665
|
+
var SOURCE_UPLOAD_METADATA_PATH = ".openpond/source-upload-metadata.json";
|
|
666
|
+
async function loadProfileActionCatalog(sourcePath) {
|
|
667
|
+
return loadProfileActionCatalogSource({
|
|
668
|
+
agentId: "default",
|
|
669
|
+
sourcePath,
|
|
670
|
+
preferred: true
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
async function loadProfileActionCatalogForSources(sources) {
|
|
674
|
+
if (sources.length === 0) {
|
|
675
|
+
return {
|
|
676
|
+
catalog: {
|
|
677
|
+
actionCount: 0,
|
|
678
|
+
generatedAt: null,
|
|
679
|
+
manifestPath: null,
|
|
680
|
+
registryPath: null,
|
|
681
|
+
stale: true,
|
|
682
|
+
error: "No profile agent sources are enabled."
|
|
683
|
+
},
|
|
684
|
+
actionCatalog: [],
|
|
685
|
+
sourceSetupRequirements: []
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
const sortedSources = [
|
|
689
|
+
...sources.filter((source) => source.preferred),
|
|
690
|
+
...sources.filter((source) => !source.preferred)
|
|
691
|
+
];
|
|
692
|
+
const results = await Promise.all(sortedSources.map(loadProfileActionCatalogSource));
|
|
693
|
+
const byId = /* @__PURE__ */ new Map();
|
|
694
|
+
const actionCatalog = [];
|
|
695
|
+
for (const result of results) {
|
|
696
|
+
for (const action of result.actionCatalog) {
|
|
697
|
+
const uniqueId = byId.has(action.id) ? `${action.agentId ?? "agent"}.${action.sourceActionId ?? action.id}` : action.id;
|
|
698
|
+
const next = uniqueId === action.id ? action : { ...action, id: uniqueId };
|
|
699
|
+
byId.set(uniqueId, next);
|
|
700
|
+
actionCatalog.push(next);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const generatedAt = latestIso(results.map((result) => result.catalog.generatedAt));
|
|
704
|
+
const errors = results.map((result) => result.catalog.error).filter(Boolean);
|
|
705
|
+
return {
|
|
706
|
+
catalog: {
|
|
707
|
+
actionCount: actionCatalog.length,
|
|
708
|
+
generatedAt,
|
|
709
|
+
manifestPath: results.find((result) => result.catalog.manifestPath)?.catalog.manifestPath ?? null,
|
|
710
|
+
registryPath: results.find((result) => result.catalog.registryPath)?.catalog.registryPath ?? null,
|
|
711
|
+
stale: results.some((result) => result.catalog.stale),
|
|
712
|
+
error: errors.length > 0 ? errors.join("; ") : null
|
|
713
|
+
},
|
|
714
|
+
actionCatalog,
|
|
715
|
+
sourceSetupRequirements: results.flatMap((result) => result.sourceSetupRequirements)
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
async function loadProfileActionCatalogSource(source) {
|
|
719
|
+
const sourcePath = source.sourcePath;
|
|
720
|
+
const manifestPath = path3.join(sourcePath, AGENT_MANIFEST_PATH);
|
|
721
|
+
const registryPath = path3.join(sourcePath, ACTION_REGISTRY_PATH);
|
|
722
|
+
const sourceUploadMetadataPath = path3.join(sourcePath, SOURCE_UPLOAD_METADATA_PATH);
|
|
723
|
+
try {
|
|
724
|
+
const manifest = await readJsonIfExists(manifestPath);
|
|
725
|
+
const registry = await readJsonIfExists(registryPath);
|
|
726
|
+
const sourceUploadMetadata = await readJsonIfExists(sourceUploadMetadataPath);
|
|
727
|
+
const sourceSetupRequirements = recordArray(asRecord(sourceUploadMetadata)?.setupRequirements) ?? [];
|
|
728
|
+
const sourceRequirementsByAction = sourceSetupRequirementsByAction(sourceSetupRequirements);
|
|
729
|
+
const byId = /* @__PURE__ */ new Map();
|
|
730
|
+
for (const record2 of [
|
|
731
|
+
...records(asRecord(manifest)?.actionCatalog),
|
|
732
|
+
...records(asRecord(registry)?.actions),
|
|
733
|
+
...records(asRecord(manifest)?.actions)
|
|
734
|
+
]) {
|
|
735
|
+
const id = text3(record2.id) ?? text3(record2.name);
|
|
736
|
+
if (!id) continue;
|
|
737
|
+
const existing = byId.get(id);
|
|
738
|
+
const actionSourceRequirements = setupRequirementsForActionSource({
|
|
739
|
+
sourceRequirementsByAction,
|
|
740
|
+
actionId: id,
|
|
741
|
+
actionName: text3(record2.name)
|
|
742
|
+
});
|
|
743
|
+
const setupRequirements = mergeSetupRequirements(
|
|
744
|
+
recordArray(record2.setupRequirements) ?? existing?.setupRequirements ?? [],
|
|
745
|
+
actionSourceRequirements
|
|
746
|
+
);
|
|
747
|
+
const next = {
|
|
748
|
+
id,
|
|
749
|
+
agentId: source.agentId,
|
|
750
|
+
sourcePath,
|
|
751
|
+
sourceActionId: id,
|
|
752
|
+
name: text3(record2.name) ?? existing?.name ?? id,
|
|
753
|
+
label: text3(record2.label) ?? existing?.label ?? titleFromActionId(id),
|
|
754
|
+
description: text3(record2.description) ?? existing?.description ?? null,
|
|
755
|
+
visibility: text3(record2.visibility) ?? existing?.visibility ?? "default",
|
|
756
|
+
inputSchema: schemaValue(record2.inputSchema) ?? existing?.inputSchema ?? null,
|
|
757
|
+
outputSchema: schemaValue(record2.outputSchema) ?? existing?.outputSchema ?? null,
|
|
758
|
+
approvalPolicy: asRecord(record2.approvalPolicy) ?? existing?.approvalPolicy ?? null,
|
|
759
|
+
artifactPolicy: asRecord(record2.artifactPolicy) ?? existing?.artifactPolicy ?? null,
|
|
760
|
+
setupRequirements,
|
|
761
|
+
mcp: asRecord(record2.mcp) ?? existing?.mcp ?? null,
|
|
762
|
+
schedulePolicy: asRecord(record2.schedulePolicy) ?? existing?.schedulePolicy ?? null,
|
|
763
|
+
trace: asRecord(record2.trace) ?? existing?.trace ?? null,
|
|
764
|
+
implementation: asRecord(record2.implementation) ?? existing?.implementation ?? null,
|
|
765
|
+
invokesModel: typeof record2.invokesModel === "boolean" ? record2.invokesModel : existing?.invokesModel
|
|
766
|
+
};
|
|
767
|
+
byId.set(id, next);
|
|
768
|
+
}
|
|
769
|
+
const actionCatalog = Array.from(byId.values()).filter(
|
|
770
|
+
(action) => action.visibility !== "internal" && action.visibility !== "debug"
|
|
771
|
+
);
|
|
772
|
+
const missingArtifacts = [
|
|
773
|
+
...!manifest ? [AGENT_MANIFEST_PATH] : [],
|
|
774
|
+
...!registry ? [ACTION_REGISTRY_PATH] : []
|
|
775
|
+
];
|
|
776
|
+
const generatedAt = await latestMtime([manifestPath, registryPath]);
|
|
777
|
+
return {
|
|
778
|
+
catalog: {
|
|
779
|
+
actionCount: actionCatalog.length,
|
|
780
|
+
generatedAt,
|
|
781
|
+
manifestPath: existsSync2(manifestPath) ? manifestPath : null,
|
|
782
|
+
registryPath: existsSync2(registryPath) ? registryPath : null,
|
|
783
|
+
stale: missingArtifacts.length > 0,
|
|
784
|
+
error: missingArtifacts.length > 0 ? missingSourceArtifactsMessage(source, missingArtifacts) : null
|
|
785
|
+
},
|
|
786
|
+
actionCatalog,
|
|
787
|
+
sourceSetupRequirements
|
|
788
|
+
};
|
|
789
|
+
} catch (error) {
|
|
790
|
+
return {
|
|
791
|
+
catalog: {
|
|
792
|
+
actionCount: 0,
|
|
793
|
+
generatedAt: null,
|
|
794
|
+
manifestPath: existsSync2(manifestPath) ? manifestPath : null,
|
|
795
|
+
registryPath: existsSync2(registryPath) ? registryPath : null,
|
|
796
|
+
stale: true,
|
|
797
|
+
error: error instanceof Error ? error.message : String(error)
|
|
798
|
+
},
|
|
799
|
+
actionCatalog: [],
|
|
800
|
+
sourceSetupRequirements: []
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function latestIso(values) {
|
|
805
|
+
let latest = 0;
|
|
806
|
+
for (const value of values) {
|
|
807
|
+
if (!value) continue;
|
|
808
|
+
const time = Date.parse(value);
|
|
809
|
+
if (Number.isFinite(time)) latest = Math.max(latest, time);
|
|
810
|
+
}
|
|
811
|
+
return latest > 0 ? new Date(latest).toISOString() : null;
|
|
812
|
+
}
|
|
813
|
+
function sourceSetupRequirementsByAction(records2) {
|
|
814
|
+
const byAction = /* @__PURE__ */ new Map();
|
|
815
|
+
for (const record2 of records2) {
|
|
816
|
+
const actionIds = [
|
|
817
|
+
text3(record2.actionId),
|
|
818
|
+
text3(record2.sourceActionId),
|
|
819
|
+
text3(record2.actionName)
|
|
820
|
+
].filter((value) => Boolean(value));
|
|
821
|
+
for (const actionId of actionIds) {
|
|
822
|
+
const current = byAction.get(actionId) ?? [];
|
|
823
|
+
current.push(record2);
|
|
824
|
+
byAction.set(actionId, current);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return byAction;
|
|
828
|
+
}
|
|
829
|
+
function setupRequirementsForActionSource(input) {
|
|
830
|
+
const matched = [
|
|
831
|
+
...input.sourceRequirementsByAction.get(input.actionId) ?? [],
|
|
832
|
+
...input.actionName && input.actionName !== input.actionId ? input.sourceRequirementsByAction.get(input.actionName) ?? [] : []
|
|
833
|
+
];
|
|
834
|
+
return matched.map((record2) => ({
|
|
835
|
+
...record2,
|
|
836
|
+
source: "source_upload_metadata"
|
|
837
|
+
}));
|
|
838
|
+
}
|
|
839
|
+
function mergeSetupRequirements(primary, sourceUploadMetadata) {
|
|
840
|
+
if (sourceUploadMetadata.length === 0) return primary;
|
|
841
|
+
const merged = [];
|
|
842
|
+
const seen = /* @__PURE__ */ new Set();
|
|
843
|
+
for (const record2 of [...primary, ...sourceUploadMetadata]) {
|
|
844
|
+
const key = setupRequirementIdentity(record2);
|
|
845
|
+
if (seen.has(key)) continue;
|
|
846
|
+
seen.add(key);
|
|
847
|
+
merged.push(record2);
|
|
848
|
+
}
|
|
849
|
+
return merged;
|
|
850
|
+
}
|
|
851
|
+
function setupRequirementIdentity(record2) {
|
|
852
|
+
return [
|
|
853
|
+
text3(record2.actionId) ?? text3(record2.sourceActionId) ?? text3(record2.actionName) ?? "",
|
|
854
|
+
text3(record2.kind) ?? text3(record2.type) ?? "",
|
|
855
|
+
text3(record2.name) ?? text3(record2.key) ?? text3(record2.provider) ?? text3(record2.tool) ?? text3(record2.command) ?? text3(record2.packageName) ?? text3(record2.path) ?? text3(record2.repo) ?? text3(record2.url) ?? text3(record2.id) ?? ""
|
|
856
|
+
].join(":");
|
|
857
|
+
}
|
|
858
|
+
function missingSourceArtifactsMessage(source, missingArtifacts) {
|
|
859
|
+
return [
|
|
860
|
+
`Profile agent ${source.agentId} at ${source.sourcePath} is missing SDK catalog artifact(s): ${missingArtifacts.join(", ")}.`,
|
|
861
|
+
"Run `openpond profile check --kind all` after source materialization so each enabled profile agent has inspect/build artifacts."
|
|
862
|
+
].join(" ");
|
|
863
|
+
}
|
|
864
|
+
async function readJsonIfExists(filePath) {
|
|
865
|
+
if (!existsSync2(filePath)) return null;
|
|
866
|
+
return JSON.parse(await readFile2(filePath, "utf8"));
|
|
867
|
+
}
|
|
868
|
+
async function latestMtime(filePaths) {
|
|
869
|
+
let latest = 0;
|
|
870
|
+
for (const filePath of filePaths) {
|
|
871
|
+
if (!existsSync2(filePath)) continue;
|
|
872
|
+
const fileStat = await stat2(filePath);
|
|
873
|
+
latest = Math.max(latest, fileStat.mtimeMs);
|
|
874
|
+
}
|
|
875
|
+
return latest > 0 ? new Date(latest).toISOString() : null;
|
|
876
|
+
}
|
|
877
|
+
function asRecord(value) {
|
|
878
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
879
|
+
}
|
|
880
|
+
function records(value) {
|
|
881
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(asRecord(item))) : [];
|
|
882
|
+
}
|
|
883
|
+
function recordArray(value) {
|
|
884
|
+
if (!Array.isArray(value)) return null;
|
|
885
|
+
return value.filter((item) => Boolean(asRecord(item)));
|
|
886
|
+
}
|
|
887
|
+
function text3(value) {
|
|
888
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
889
|
+
}
|
|
890
|
+
function schemaValue(value) {
|
|
891
|
+
return text3(value) ?? asRecord(value);
|
|
892
|
+
}
|
|
893
|
+
function titleFromActionId(id) {
|
|
894
|
+
const title = id.replace(/[._-]+/g, " ").trim();
|
|
895
|
+
return title ? title.replace(/\b\w/g, (letter) => letter.toUpperCase()) : id;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// ../../packages/cloud/src/profile/profile-skills.ts
|
|
899
|
+
import { createHash } from "node:crypto";
|
|
900
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
901
|
+
import { readdir, readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
902
|
+
import path4 from "node:path";
|
|
903
|
+
var PROFILE_SKILLS_DIR = "skills";
|
|
904
|
+
var PROFILE_SKILL_FILE = "SKILL.md";
|
|
905
|
+
var PROFILE_SKILL_MAX_CHARS = 8e4;
|
|
906
|
+
async function loadProfileSkills(profileSourcePath2) {
|
|
907
|
+
const skillsDir = path4.join(profileSourcePath2, PROFILE_SKILLS_DIR);
|
|
908
|
+
if (!existsSync3(skillsDir)) {
|
|
909
|
+
return {
|
|
910
|
+
skillCatalog: emptyProfileSkillCatalogState2({ stale: false }),
|
|
911
|
+
skills: []
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
const entries = await readdir(skillsDir, { withFileTypes: true });
|
|
916
|
+
const skills = (await Promise.all(
|
|
917
|
+
entries.filter((entry) => entry.isDirectory()).map((entry) => loadProfileSkillDirectory(profileSourcePath2, entry.name))
|
|
918
|
+
)).sort((left, right) => left.name.localeCompare(right.name) || left.path.localeCompare(right.path));
|
|
919
|
+
markDuplicateSkillNames(skills);
|
|
920
|
+
const generatedAt = await latestSkillMtime(profileSourcePath2, skills);
|
|
921
|
+
return {
|
|
922
|
+
skillCatalog: {
|
|
923
|
+
skillCount: skills.length,
|
|
924
|
+
generatedAt,
|
|
925
|
+
stale: false,
|
|
926
|
+
error: null
|
|
927
|
+
},
|
|
928
|
+
skills
|
|
929
|
+
};
|
|
930
|
+
} catch (error) {
|
|
931
|
+
return {
|
|
932
|
+
skillCatalog: emptyProfileSkillCatalogState2({
|
|
933
|
+
stale: true,
|
|
934
|
+
error: error instanceof Error ? error.message : String(error)
|
|
935
|
+
}),
|
|
936
|
+
skills: []
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
function parseProfileSkillMarkdown(markdown) {
|
|
941
|
+
const messages = [];
|
|
942
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/.exec(markdown);
|
|
943
|
+
if (!match) {
|
|
944
|
+
return {
|
|
945
|
+
name: null,
|
|
946
|
+
description: null,
|
|
947
|
+
body: markdown.trim(),
|
|
948
|
+
messages: ["SKILL.md must start with YAML frontmatter bounded by ---."]
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
const frontmatterSource = match[1] ?? "";
|
|
952
|
+
const body = (match[2] ?? "").trim();
|
|
953
|
+
let frontmatter;
|
|
954
|
+
try {
|
|
955
|
+
frontmatter = yaml.load(frontmatterSource);
|
|
956
|
+
} catch (error) {
|
|
957
|
+
messages.push(`SKILL.md frontmatter is invalid YAML: ${error instanceof Error ? error.message : String(error)}`);
|
|
958
|
+
}
|
|
959
|
+
const record2 = asRecord2(frontmatter);
|
|
960
|
+
const name = text4(record2?.name);
|
|
961
|
+
const description = text4(record2?.description);
|
|
962
|
+
if (!name) messages.push("Skill frontmatter must include a non-empty name.");
|
|
963
|
+
if (!description) messages.push("Skill frontmatter must include a non-empty description.");
|
|
964
|
+
if (!body) messages.push("Skill body must be non-empty markdown.");
|
|
965
|
+
return {
|
|
966
|
+
name,
|
|
967
|
+
description,
|
|
968
|
+
body,
|
|
969
|
+
messages
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
function emptyProfileSkillCatalogState2(input = {}) {
|
|
973
|
+
return {
|
|
974
|
+
skillCount: 0,
|
|
975
|
+
generatedAt: null,
|
|
976
|
+
stale: input.stale ?? true,
|
|
977
|
+
error: input.error ?? null
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
async function loadProfileSkillDirectory(profileSourcePath2, directoryName) {
|
|
981
|
+
const relativePath = path4.join(PROFILE_SKILLS_DIR, directoryName, PROFILE_SKILL_FILE).replace(/\\/g, "/");
|
|
982
|
+
const absolutePath = path4.join(profileSourcePath2, relativePath);
|
|
983
|
+
const messages = [];
|
|
984
|
+
const entries = await readdir(path4.join(profileSourcePath2, PROFILE_SKILLS_DIR, directoryName), { withFileTypes: true });
|
|
985
|
+
for (const entry of entries) {
|
|
986
|
+
if (entry.name === PROFILE_SKILL_FILE && entry.isFile()) continue;
|
|
987
|
+
messages.push(`Profile skills only support ${PROFILE_SKILL_FILE}; unsupported entry: ${entry.name}`);
|
|
988
|
+
}
|
|
989
|
+
let contents = "";
|
|
990
|
+
if (!existsSync3(absolutePath)) {
|
|
991
|
+
messages.push(`Missing ${PROFILE_SKILL_FILE}.`);
|
|
992
|
+
} else {
|
|
993
|
+
contents = await readFile3(absolutePath, "utf8");
|
|
994
|
+
}
|
|
995
|
+
if (contents.length > PROFILE_SKILL_MAX_CHARS) {
|
|
996
|
+
messages.push(`Skill markdown exceeds the limit of ${PROFILE_SKILL_MAX_CHARS} characters.`);
|
|
997
|
+
}
|
|
998
|
+
const parsed = parseProfileSkillMarkdown(contents);
|
|
999
|
+
messages.push(...parsed.messages);
|
|
1000
|
+
const name = parsed.name ?? directoryName;
|
|
1001
|
+
const description = parsed.description ?? "";
|
|
1002
|
+
if (!isKebabCaseSkillName(name)) {
|
|
1003
|
+
messages.push("Skill name must be lowercase kebab-case.");
|
|
1004
|
+
}
|
|
1005
|
+
if (name !== directoryName) {
|
|
1006
|
+
messages.push(`Skill name must match its directory name (${directoryName}).`);
|
|
1007
|
+
}
|
|
1008
|
+
return {
|
|
1009
|
+
name,
|
|
1010
|
+
description,
|
|
1011
|
+
path: relativePath,
|
|
1012
|
+
scope: "profile",
|
|
1013
|
+
enabled: messages.length === 0,
|
|
1014
|
+
sourcePath: profileSourcePath2,
|
|
1015
|
+
charCount: contents.length,
|
|
1016
|
+
sourceHash: sha256Hex(contents),
|
|
1017
|
+
validationStatus: messages.length > 0 ? "error" : "valid",
|
|
1018
|
+
validationMessages: messages
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
function markDuplicateSkillNames(skills) {
|
|
1022
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1023
|
+
for (const skill of skills) {
|
|
1024
|
+
counts.set(skill.name, (counts.get(skill.name) ?? 0) + 1);
|
|
1025
|
+
}
|
|
1026
|
+
for (const skill of skills) {
|
|
1027
|
+
if ((counts.get(skill.name) ?? 0) <= 1) continue;
|
|
1028
|
+
skill.enabled = false;
|
|
1029
|
+
skill.validationStatus = "error";
|
|
1030
|
+
skill.validationMessages = [
|
|
1031
|
+
...skill.validationMessages,
|
|
1032
|
+
`Duplicate skill name: ${skill.name}`
|
|
1033
|
+
];
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
async function latestSkillMtime(profileSourcePath2, skills) {
|
|
1037
|
+
let latest = 0;
|
|
1038
|
+
for (const skill of skills) {
|
|
1039
|
+
const fileStat = await stat3(path4.join(profileSourcePath2, skill.path)).catch(() => null);
|
|
1040
|
+
if (!fileStat?.isFile()) continue;
|
|
1041
|
+
latest = Math.max(latest, fileStat.mtimeMs);
|
|
1042
|
+
}
|
|
1043
|
+
return latest > 0 ? new Date(latest).toISOString() : null;
|
|
1044
|
+
}
|
|
1045
|
+
function isKebabCaseSkillName(value) {
|
|
1046
|
+
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
|
|
1047
|
+
}
|
|
1048
|
+
function asRecord2(value) {
|
|
1049
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1050
|
+
}
|
|
1051
|
+
function text4(value) {
|
|
1052
|
+
if (typeof value !== "string") return null;
|
|
1053
|
+
const trimmed = value.trim();
|
|
1054
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1055
|
+
}
|
|
1056
|
+
function sha256Hex(contents) {
|
|
1057
|
+
return createHash("sha256").update(contents).digest("hex");
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// ../../packages/cloud/src/profile/local-profile.ts
|
|
1061
|
+
var PROFILE_REPO_MANIFEST = "openpond-profile.json";
|
|
1062
|
+
var PROFILE_MANIFEST = "settings/profile.yaml";
|
|
1063
|
+
var DEFAULT_LOCAL_PROFILE = "default";
|
|
1064
|
+
var DEFAULT_PROFILE_AGENT = "default";
|
|
1065
|
+
function mergeProfileRepoManifestEntry(existing, profilePath) {
|
|
1066
|
+
const defaultAgent = existing?.defaultAgent ?? DEFAULT_PROFILE_AGENT;
|
|
1067
|
+
const enabledAgents = existing?.enabledAgents && existing.enabledAgents.length > 0 ? existing.enabledAgents : [defaultAgent];
|
|
1068
|
+
return {
|
|
1069
|
+
...existing ?? {},
|
|
1070
|
+
path: profilePath,
|
|
1071
|
+
defaultAgent,
|
|
1072
|
+
enabledAgents
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
function mergeActiveLocalProfileConfig(existing, repoPath, profile) {
|
|
1076
|
+
const next = {
|
|
1077
|
+
repoPath,
|
|
1078
|
+
profile,
|
|
1079
|
+
mode: "local"
|
|
1080
|
+
};
|
|
1081
|
+
if (!existing) return next;
|
|
1082
|
+
if (path5.resolve(existing.repoPath) !== path5.resolve(repoPath) || existing.profile !== profile) {
|
|
1083
|
+
return next;
|
|
1084
|
+
}
|
|
1085
|
+
return {
|
|
1086
|
+
...existing,
|
|
1087
|
+
...next
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
var require2 = createRequire(import.meta.url);
|
|
1091
|
+
function defaultLocalProfileRepoPath() {
|
|
1092
|
+
return path5.join(os.homedir(), ".openpond", "profiles", "default-repo");
|
|
1093
|
+
}
|
|
1094
|
+
function emptyProfileState(error = null) {
|
|
1095
|
+
return {
|
|
1096
|
+
mode: "none",
|
|
1097
|
+
repoPath: null,
|
|
1098
|
+
activeProfile: null,
|
|
1099
|
+
sourcePath: null,
|
|
1100
|
+
manifestPath: null,
|
|
1101
|
+
agents: [],
|
|
1102
|
+
skills: [],
|
|
1103
|
+
git: null,
|
|
1104
|
+
catalog: emptyProfileCatalogState(error),
|
|
1105
|
+
skillCatalog: emptyProfileSkillCatalogState(error),
|
|
1106
|
+
actionCatalog: [],
|
|
1107
|
+
sourceSetupRequirements: [],
|
|
1108
|
+
setupGate: emptyProfileSetupGate(),
|
|
1109
|
+
diff: emptyProfileDiffSummary(),
|
|
1110
|
+
hosted: null,
|
|
1111
|
+
summary: emptyProfileSummary(error),
|
|
1112
|
+
lastCheck: null,
|
|
1113
|
+
error
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
async function initLocalProfileRepo(input = {}) {
|
|
1117
|
+
const repoPath = path5.resolve(input.repoPath ?? defaultLocalProfileRepoPath());
|
|
1118
|
+
const profile = normalizeProfileName(input.profile);
|
|
1119
|
+
const profilePath = path5.join("profiles", profile);
|
|
1120
|
+
const profileSourcePath2 = path5.join(repoPath, profilePath);
|
|
1121
|
+
await mkdir(repoPath, { recursive: true });
|
|
1122
|
+
const manifest = await readProfileManifest(repoPath).catch(() => defaultProfileManifest(profile, profilePath));
|
|
1123
|
+
manifest.defaultProfile = manifest.defaultProfile || profile;
|
|
1124
|
+
manifest.profiles[profile] = mergeProfileRepoManifestEntry(manifest.profiles[profile], profilePath);
|
|
1125
|
+
await ensureProfileSource(profileSourcePath2, input.template ?? "blank-agent", Boolean(input.force));
|
|
1126
|
+
await ensureProfileDependencies(profileSourcePath2);
|
|
1127
|
+
await ensureProfileScaffoldFiles(repoPath, profileSourcePath2, profile);
|
|
1128
|
+
await writeProfileManifest(repoPath, manifest);
|
|
1129
|
+
await ensureProfileGitRepo(repoPath);
|
|
1130
|
+
const currentConfig = (await loadGlobalConfig()).openpondProfile;
|
|
1131
|
+
await saveGlobalConfig({
|
|
1132
|
+
openpondProfile: mergeActiveLocalProfileConfig(currentConfig, repoPath, profile)
|
|
1133
|
+
});
|
|
1134
|
+
return loadOpenPondProfileState();
|
|
1135
|
+
}
|
|
1136
|
+
async function loadLocalProfileRepo(repoPathInput, profileInput) {
|
|
1137
|
+
const repoPath = path5.resolve(repoPathInput);
|
|
1138
|
+
const manifest = await readProfileManifest(repoPath);
|
|
1139
|
+
const profile = normalizeProfileName(profileInput ?? manifest.defaultProfile);
|
|
1140
|
+
profileSourcePath(manifest, repoPath, profile);
|
|
1141
|
+
const currentConfig = (await loadGlobalConfig()).openpondProfile;
|
|
1142
|
+
await saveGlobalConfig({
|
|
1143
|
+
openpondProfile: mergeActiveLocalProfileConfig(currentConfig, repoPath, profile)
|
|
1144
|
+
});
|
|
1145
|
+
return loadOpenPondProfileState();
|
|
1146
|
+
}
|
|
1147
|
+
async function loadOpenPondProfileState() {
|
|
1148
|
+
const config = await loadGlobalConfig();
|
|
1149
|
+
const active = config.openpondProfile;
|
|
1150
|
+
if (!active) return emptyProfileState();
|
|
1151
|
+
try {
|
|
1152
|
+
const manifest = await readProfileManifest(active.repoPath);
|
|
1153
|
+
const sourcePath = profileSourcePath(manifest, active.repoPath, active.profile);
|
|
1154
|
+
const agents = await listProfileAgents(manifest, active.profile);
|
|
1155
|
+
const [git, catalogResult, skillResult] = await Promise.all([
|
|
1156
|
+
loadProfileGitState(active.repoPath),
|
|
1157
|
+
loadProfileActionCatalogForSources(
|
|
1158
|
+
profileCatalogSources({
|
|
1159
|
+
manifest,
|
|
1160
|
+
profile: active.profile,
|
|
1161
|
+
profileSourcePath: sourcePath,
|
|
1162
|
+
agents
|
|
1163
|
+
})
|
|
1164
|
+
),
|
|
1165
|
+
loadProfileSkills(sourcePath)
|
|
1166
|
+
]);
|
|
1167
|
+
const hosted = hostedBindingFromConfig(active);
|
|
1168
|
+
const diff = summarizeProfileDiff(git, active.profile);
|
|
1169
|
+
const setupGate = buildOpenPondProfileSetupGate({
|
|
1170
|
+
actionCatalog: catalogResult.actionCatalog,
|
|
1171
|
+
sourceSetupRequirements: catalogResult.sourceSetupRequirements
|
|
1172
|
+
});
|
|
1173
|
+
const summary = summarizeProfileState({
|
|
1174
|
+
agents,
|
|
1175
|
+
actionCatalog: catalogResult.actionCatalog,
|
|
1176
|
+
catalog: catalogResult.catalog,
|
|
1177
|
+
diff,
|
|
1178
|
+
git,
|
|
1179
|
+
hosted,
|
|
1180
|
+
lastCheck: active.lastCheck ?? null,
|
|
1181
|
+
error: null
|
|
1182
|
+
});
|
|
1183
|
+
return {
|
|
1184
|
+
mode: "local",
|
|
1185
|
+
repoPath: active.repoPath,
|
|
1186
|
+
activeProfile: active.profile,
|
|
1187
|
+
sourcePath,
|
|
1188
|
+
manifestPath: path5.join(active.repoPath, PROFILE_REPO_MANIFEST),
|
|
1189
|
+
agents,
|
|
1190
|
+
skills: skillResult.skills,
|
|
1191
|
+
git,
|
|
1192
|
+
catalog: catalogResult.catalog,
|
|
1193
|
+
skillCatalog: skillResult.skillCatalog,
|
|
1194
|
+
actionCatalog: catalogResult.actionCatalog,
|
|
1195
|
+
sourceSetupRequirements: catalogResult.sourceSetupRequirements,
|
|
1196
|
+
setupGate,
|
|
1197
|
+
diff,
|
|
1198
|
+
hosted,
|
|
1199
|
+
summary,
|
|
1200
|
+
lastCheck: active.lastCheck ?? null,
|
|
1201
|
+
error: null
|
|
1202
|
+
};
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
const git = await loadProfileGitState(active.repoPath).catch(() => null);
|
|
1205
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1206
|
+
return {
|
|
1207
|
+
mode: "local",
|
|
1208
|
+
repoPath: active.repoPath,
|
|
1209
|
+
activeProfile: active.profile,
|
|
1210
|
+
sourcePath: null,
|
|
1211
|
+
manifestPath: path5.join(active.repoPath, PROFILE_REPO_MANIFEST),
|
|
1212
|
+
agents: [],
|
|
1213
|
+
skills: [],
|
|
1214
|
+
git,
|
|
1215
|
+
catalog: emptyProfileCatalogState(errorMessage),
|
|
1216
|
+
skillCatalog: emptyProfileSkillCatalogState(errorMessage),
|
|
1217
|
+
actionCatalog: [],
|
|
1218
|
+
sourceSetupRequirements: [],
|
|
1219
|
+
setupGate: emptyProfileSetupGate(),
|
|
1220
|
+
diff: emptyProfileDiffSummary(),
|
|
1221
|
+
hosted: hostedBindingFromConfig(active),
|
|
1222
|
+
summary: emptyProfileSummary(errorMessage),
|
|
1223
|
+
lastCheck: active.lastCheck ?? null,
|
|
1224
|
+
error: errorMessage
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async function requireActiveLocalProfile() {
|
|
1229
|
+
const config = (await loadGlobalConfig()).openpondProfile;
|
|
1230
|
+
if (!config) {
|
|
1231
|
+
throw new Error("No active OpenPond profile. Run `openpond init` or `openpond profile load --path <dir>`.");
|
|
1232
|
+
}
|
|
1233
|
+
const state = await loadOpenPondProfileState();
|
|
1234
|
+
if (state.error) throw new Error(state.error);
|
|
1235
|
+
if (!state.sourcePath) throw new Error("Active OpenPond profile has no source path.");
|
|
1236
|
+
return { config, state };
|
|
1237
|
+
}
|
|
1238
|
+
async function runProfileSdkCommand(input) {
|
|
1239
|
+
const active = await requireActiveLocalProfile();
|
|
1240
|
+
const requestedRunArgs = input.command === "run" ? input.args ?? [] : [];
|
|
1241
|
+
const selectedAction = input.command === "run" ? active.state.actionCatalog.find((action) => action.id === requestedRunArgs[0]) : null;
|
|
1242
|
+
const cwd = path5.resolve(
|
|
1243
|
+
input.cwd ?? selectedAction?.sourcePath ?? active.state.sourcePath
|
|
1244
|
+
);
|
|
1245
|
+
const runArgs = input.command === "run" ? runCommandArgs(cwd, input.args ?? [], selectedAction?.sourceActionId ?? null) : null;
|
|
1246
|
+
if (input.command === "run" && runArgs) {
|
|
1247
|
+
const actionId = runArgs[0];
|
|
1248
|
+
const catalog = path5.resolve(active.state.sourcePath) === cwd ? {
|
|
1249
|
+
actionCatalog: active.state.actionCatalog,
|
|
1250
|
+
sourceSetupRequirements: active.state.sourceSetupRequirements
|
|
1251
|
+
} : await loadProfileActionCatalog(cwd);
|
|
1252
|
+
assertOpenPondProfileActionReady(
|
|
1253
|
+
actionId,
|
|
1254
|
+
buildOpenPondProfileSetupGate({
|
|
1255
|
+
actionCatalog: catalog.actionCatalog,
|
|
1256
|
+
sourceSetupRequirements: catalog.sourceSetupRequirements,
|
|
1257
|
+
actionId
|
|
1258
|
+
})
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
const result = await runAgentSdkProjectCommand({
|
|
1262
|
+
command: input.command,
|
|
1263
|
+
args: input.command === "run" ? runArgs ?? runCommandArgs(cwd, input.args ?? []) : input.args,
|
|
1264
|
+
cwd,
|
|
1265
|
+
inherit: input.inherit,
|
|
1266
|
+
throwOnFailure: false
|
|
1267
|
+
});
|
|
1268
|
+
await saveProfileCheckStatus(input.command, result.code);
|
|
1269
|
+
if (result.code !== 0) {
|
|
1270
|
+
const detail = result.stderr.trim() || result.stdout.trim();
|
|
1271
|
+
throw new Error(detail || `${input.command} failed with exit code ${result.code ?? "unknown"}`);
|
|
1272
|
+
}
|
|
1273
|
+
return result;
|
|
1274
|
+
}
|
|
1275
|
+
async function runAgentSdkProjectCommand(input) {
|
|
1276
|
+
const cwd = path5.resolve(input.cwd);
|
|
1277
|
+
const cli = resolveAgentSdkCliLaunch();
|
|
1278
|
+
const args = input.command === "run" ? [...cli.args, input.command, ...input.args ?? []] : [...cli.args, input.command, "--cwd", cwd, ...input.args ?? []];
|
|
1279
|
+
const result = await spawnCommand(cli.command, args, {
|
|
1280
|
+
cwd,
|
|
1281
|
+
inherit: input.inherit,
|
|
1282
|
+
timeoutMs: input.timeoutMs,
|
|
1283
|
+
maxOutputBytes: input.maxOutputBytes
|
|
1284
|
+
});
|
|
1285
|
+
if (input.throwOnFailure !== false && result.code !== 0) {
|
|
1286
|
+
const detail = commandFailureDetail(result, input.command);
|
|
1287
|
+
throw new Error(detail || `${input.command} failed with exit code ${result.code ?? "unknown"}`);
|
|
1288
|
+
}
|
|
1289
|
+
return result;
|
|
1290
|
+
}
|
|
1291
|
+
function runCommandArgs(cwd, args, sourceActionId = null) {
|
|
1292
|
+
const [actionName, ...rest] = args;
|
|
1293
|
+
if (!actionName || actionName.startsWith("--")) {
|
|
1294
|
+
throw new Error("run requires an action name");
|
|
1295
|
+
}
|
|
1296
|
+
return [sourceActionId ?? actionName, "--cwd", cwd, ...rest];
|
|
1297
|
+
}
|
|
1298
|
+
async function runProfileCheck(kind) {
|
|
1299
|
+
const checks = !kind || kind === "all" ? ["inspect", "build", "validate", "eval"] : [kind];
|
|
1300
|
+
const active = await requireActiveLocalProfile();
|
|
1301
|
+
const manifest = await readProfileManifest(active.config.repoPath);
|
|
1302
|
+
const sources = profileCatalogSources({
|
|
1303
|
+
manifest,
|
|
1304
|
+
profile: active.config.profile,
|
|
1305
|
+
profileSourcePath: active.state.sourcePath,
|
|
1306
|
+
agents: active.state.agents
|
|
1307
|
+
});
|
|
1308
|
+
for (const command of checks) {
|
|
1309
|
+
if (command !== "inspect" && command !== "build" && command !== "validate" && command !== "eval") {
|
|
1310
|
+
throw new Error("check kind must be one of inspect, build, validate, eval, all");
|
|
1311
|
+
}
|
|
1312
|
+
for (const source of sources) {
|
|
1313
|
+
await runProfileCheckForSource({
|
|
1314
|
+
command,
|
|
1315
|
+
source,
|
|
1316
|
+
profileSourcePath: active.state.sourcePath,
|
|
1317
|
+
sourceCount: sources.length
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
async function runProfileCheckForSource(input) {
|
|
1323
|
+
const args = input.command === "inspect" || input.command === "validate" || input.command === "eval" ? ["--json"] : [];
|
|
1324
|
+
if (input.sourceCount > 1) {
|
|
1325
|
+
const label = path5.relative(input.profileSourcePath, input.source.sourcePath) || ".";
|
|
1326
|
+
console.log(`
|
|
1327
|
+
[profile:${input.source.agentId}] ${input.command} ${label}`);
|
|
1328
|
+
}
|
|
1329
|
+
const result = await runAgentSdkProjectCommand({
|
|
1330
|
+
command: input.command,
|
|
1331
|
+
args,
|
|
1332
|
+
cwd: input.source.sourcePath,
|
|
1333
|
+
inherit: false,
|
|
1334
|
+
throwOnFailure: false
|
|
1335
|
+
});
|
|
1336
|
+
if (result.stdout.trim()) console.log(result.stdout.trim());
|
|
1337
|
+
if (result.stderr.trim()) console.error(result.stderr.trim());
|
|
1338
|
+
await saveProfileCheckStatus(input.command, result.code);
|
|
1339
|
+
if (result.code !== 0) {
|
|
1340
|
+
const detail = commandFailureDetail(result, input.command);
|
|
1341
|
+
throw new Error(
|
|
1342
|
+
[
|
|
1343
|
+
`Profile check failed for enabled agent ${input.source.agentId} at ${input.source.sourcePath}.`,
|
|
1344
|
+
detail || `${input.command} failed with exit code ${result.code ?? "unknown"}`
|
|
1345
|
+
].join("\n")
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
async function commitActiveProfileChanges(message) {
|
|
1350
|
+
const active = await requireActiveLocalProfile();
|
|
1351
|
+
if (!active.state.repoPath) {
|
|
1352
|
+
throw new Error("Active OpenPond profile has no repo path.");
|
|
1353
|
+
}
|
|
1354
|
+
const result = await commitProfileChanges(
|
|
1355
|
+
active.state.repoPath,
|
|
1356
|
+
message?.trim() || `Update OpenPond profile ${active.state.activeProfile ?? DEFAULT_LOCAL_PROFILE}`
|
|
1357
|
+
);
|
|
1358
|
+
return {
|
|
1359
|
+
committed: result.committed,
|
|
1360
|
+
stdout: result.stdout,
|
|
1361
|
+
stderr: result.stderr,
|
|
1362
|
+
state: await loadOpenPondProfileState()
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
async function saveProfilePushStatus(status) {
|
|
1366
|
+
const config = (await loadGlobalConfig()).openpondProfile;
|
|
1367
|
+
if (!config) return;
|
|
1368
|
+
await saveGlobalConfig({
|
|
1369
|
+
openpondProfile: {
|
|
1370
|
+
...config,
|
|
1371
|
+
lastPush: status
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
async function saveProfileCheckStatus(command, exitCode) {
|
|
1376
|
+
const config = (await loadGlobalConfig()).openpondProfile;
|
|
1377
|
+
if (!config) return;
|
|
1378
|
+
const sourceHead = await profileGitHead(config.repoPath).catch(() => null);
|
|
1379
|
+
await saveGlobalConfig({
|
|
1380
|
+
openpondProfile: {
|
|
1381
|
+
...config,
|
|
1382
|
+
lastCheck: {
|
|
1383
|
+
command,
|
|
1384
|
+
status: exitCode === 0 ? "passed" : "failed",
|
|
1385
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1386
|
+
exitCode,
|
|
1387
|
+
sourceHead
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
async function ensureProfileSource(profileSourcePath2, template, force) {
|
|
1393
|
+
await mkdir(profileSourcePath2, { recursive: true });
|
|
1394
|
+
const entries = await readdir2(profileSourcePath2);
|
|
1395
|
+
if (entries.length > 0 && !force) return;
|
|
1396
|
+
const sdkRoot = resolveAgentSdkRoot();
|
|
1397
|
+
const templateDir = path5.join(sdkRoot, "templates", template);
|
|
1398
|
+
if (!existsSync4(templateDir)) {
|
|
1399
|
+
throw new Error(`Unknown agent template "${template}".`);
|
|
1400
|
+
}
|
|
1401
|
+
await cp(templateDir, profileSourcePath2, {
|
|
1402
|
+
recursive: true,
|
|
1403
|
+
force,
|
|
1404
|
+
errorOnExist: false
|
|
1405
|
+
});
|
|
1406
|
+
await rewriteTemplateSdkDependency(profileSourcePath2, sdkRoot);
|
|
1407
|
+
}
|
|
1408
|
+
async function ensureProfileScaffoldFiles(repoPath, profileSourcePath2, profile) {
|
|
1409
|
+
await mkdir(path5.join(repoPath, "profiles"), { recursive: true });
|
|
1410
|
+
for (const dir of ["agents", "skills", "actions", "extensions", "prompts", "goals", "evals", "settings", "traces"]) {
|
|
1411
|
+
await mkdir(path5.join(profileSourcePath2, dir), { recursive: true });
|
|
1412
|
+
}
|
|
1413
|
+
const profileManifestPath = path5.join(profileSourcePath2, PROFILE_MANIFEST);
|
|
1414
|
+
if (!existsSync4(profileManifestPath)) {
|
|
1415
|
+
await writeFile2(
|
|
1416
|
+
profileManifestPath,
|
|
1417
|
+
[
|
|
1418
|
+
"schema: openpond.profile.v1",
|
|
1419
|
+
`profile: ${profile}`,
|
|
1420
|
+
"agents:",
|
|
1421
|
+
` - id: ${DEFAULT_PROFILE_AGENT}`,
|
|
1422
|
+
" path: agent/agent.ts",
|
|
1423
|
+
" enabled: true",
|
|
1424
|
+
""
|
|
1425
|
+
].join("\n"),
|
|
1426
|
+
"utf8"
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
const lockPath = path5.join(profileSourcePath2, "openpond.lock");
|
|
1430
|
+
if (!existsSync4(lockPath)) {
|
|
1431
|
+
await writeFile2(lockPath, "# OpenPond profile lockfile placeholder\n", "utf8");
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
async function rewriteTemplateSdkDependency(profileSourcePath2, sdkRoot) {
|
|
1435
|
+
const packageJsonPath = path5.join(profileSourcePath2, "package.json");
|
|
1436
|
+
const raw = await readFile4(packageJsonPath, "utf8");
|
|
1437
|
+
const packageJson = JSON.parse(raw);
|
|
1438
|
+
packageJson.dependencies = {
|
|
1439
|
+
...packageJson.dependencies ?? {},
|
|
1440
|
+
"openpond-agent-sdk": `file:${sdkRoot}`
|
|
1441
|
+
};
|
|
1442
|
+
await writeFile2(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
|
|
1443
|
+
`, "utf8");
|
|
1444
|
+
}
|
|
1445
|
+
async function ensureProfileDependencies(profileSourcePath2) {
|
|
1446
|
+
if (!existsSync4(path5.join(profileSourcePath2, "package.json"))) return;
|
|
1447
|
+
const sdkRoot = resolveAgentSdkRoot();
|
|
1448
|
+
await ensureAgentSdkBuilt(sdkRoot);
|
|
1449
|
+
const nodeModulesPath = path5.join(profileSourcePath2, "node_modules");
|
|
1450
|
+
const sdkLinkPath = path5.join(nodeModulesPath, "openpond-agent-sdk");
|
|
1451
|
+
await mkdir(nodeModulesPath, { recursive: true });
|
|
1452
|
+
if (!existsSync4(sdkLinkPath)) {
|
|
1453
|
+
await symlink(sdkRoot, sdkLinkPath, "dir");
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
async function ensureAgentSdkBuilt(sdkRoot) {
|
|
1457
|
+
if (existsSync4(path5.join(sdkRoot, "dist", "cli.js")) && existsSync4(path5.join(sdkRoot, "dist", "primitives", "index.js"))) {
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
const buildScript = path5.join(sdkRoot, "scripts", "build.ts");
|
|
1461
|
+
if (!existsSync4(buildScript)) {
|
|
1462
|
+
throw new Error(`Resolved openpond-agent-sdk at ${sdkRoot}, but built SDK files are missing.`);
|
|
1463
|
+
}
|
|
1464
|
+
const result = await spawnCommand(
|
|
1465
|
+
process.execPath,
|
|
1466
|
+
[resolveTsxCli(sdkRoot), buildScript],
|
|
1467
|
+
{ cwd: sdkRoot }
|
|
1468
|
+
);
|
|
1469
|
+
if (result.code !== 0) {
|
|
1470
|
+
const detail = result.stderr.trim() || result.stdout.trim();
|
|
1471
|
+
throw new Error(detail || `openpond-agent-sdk build failed with exit code ${result.code ?? "unknown"}`);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
async function listProfileAgents(manifest, profile) {
|
|
1475
|
+
const profileConfig = manifest.profiles[profile];
|
|
1476
|
+
if (!profileConfig) return [];
|
|
1477
|
+
const defaultAgent = profileConfig.defaultAgent ?? DEFAULT_PROFILE_AGENT;
|
|
1478
|
+
const enabled = new Set(profileConfig.enabledAgents ?? [defaultAgent]);
|
|
1479
|
+
return Array.from(enabled).map((id) => ({
|
|
1480
|
+
id,
|
|
1481
|
+
name: id,
|
|
1482
|
+
path: id === DEFAULT_PROFILE_AGENT ? "agent/agent.ts" : `agents/${id}`,
|
|
1483
|
+
enabled: true
|
|
1484
|
+
}));
|
|
1485
|
+
}
|
|
1486
|
+
function profileCatalogSources(input) {
|
|
1487
|
+
const defaultAgent = input.manifest.profiles[input.profile]?.defaultAgent ?? DEFAULT_PROFILE_AGENT;
|
|
1488
|
+
const agents = input.agents.length > 0 ? input.agents : [{
|
|
1489
|
+
id: DEFAULT_PROFILE_AGENT,
|
|
1490
|
+
name: DEFAULT_PROFILE_AGENT,
|
|
1491
|
+
path: "agent/agent.ts",
|
|
1492
|
+
enabled: true
|
|
1493
|
+
}];
|
|
1494
|
+
return agents.map((agent) => ({
|
|
1495
|
+
agentId: agent.id,
|
|
1496
|
+
sourcePath: agent.id === DEFAULT_PROFILE_AGENT ? input.profileSourcePath : path5.join(input.profileSourcePath, "agents", agent.id),
|
|
1497
|
+
preferred: agent.id === defaultAgent
|
|
1498
|
+
}));
|
|
1499
|
+
}
|
|
1500
|
+
function hostedBindingFromConfig(config) {
|
|
1501
|
+
const push = config.lastPush;
|
|
1502
|
+
if (!push) return null;
|
|
1503
|
+
return {
|
|
1504
|
+
teamId: push.teamId ?? null,
|
|
1505
|
+
projectId: push.projectId ?? null,
|
|
1506
|
+
sourceRef: push.sourceRef ?? null,
|
|
1507
|
+
sourceCommitSha: push.hostedHead ?? null,
|
|
1508
|
+
lastPushedAt: push.pushedAt,
|
|
1509
|
+
lastPushedLocalHead: push.localHead ?? null,
|
|
1510
|
+
lastPushedHostedHead: push.hostedHead ?? null,
|
|
1511
|
+
promotionStatus: push.promotionStatus ?? null,
|
|
1512
|
+
hostedRunStatus: push.hostedRunStatus ?? null,
|
|
1513
|
+
localGoalId: push.localGoalId ?? null,
|
|
1514
|
+
hostedGoalId: push.hostedGoalId ?? null,
|
|
1515
|
+
hostedRunAgentId: push.hostedRunAgentId ?? null,
|
|
1516
|
+
hostedRunId: push.hostedRunId ?? null,
|
|
1517
|
+
hostedRunAt: push.hostedRunAt ?? null,
|
|
1518
|
+
hostedSourceMaterialization: push.hostedSourceMaterialization ?? null,
|
|
1519
|
+
hostedSourceCheck: push.hostedSourceCheck ?? null,
|
|
1520
|
+
hostedPublish: push.hostedPublish ?? null,
|
|
1521
|
+
hostedRun: push.hostedRun ?? null
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
function summarizeProfileState(input) {
|
|
1525
|
+
const defaultAction = input.actionCatalog.find((action) => action.id === "chat")?.id ?? input.actionCatalog.find((action) => action.name === "chat")?.id ?? input.actionCatalog.find((action) => action.id.endsWith(".chat"))?.id ?? null;
|
|
1526
|
+
const checkFresh = Boolean(
|
|
1527
|
+
input.lastCheck && input.lastCheck.status === "passed" && input.lastCheck.sourceHead && input.lastCheck.sourceHead === input.git.head && !input.git.dirty && !input.catalog.stale
|
|
1528
|
+
);
|
|
1529
|
+
const checkStaleReason = profileCheckStaleReason({
|
|
1530
|
+
catalog: input.catalog,
|
|
1531
|
+
git: input.git,
|
|
1532
|
+
lastCheck: input.lastCheck
|
|
1533
|
+
});
|
|
1534
|
+
if (input.error) {
|
|
1535
|
+
return {
|
|
1536
|
+
state: "error",
|
|
1537
|
+
message: input.error,
|
|
1538
|
+
agentCount: input.agents.length,
|
|
1539
|
+
actionCount: input.actionCatalog.length,
|
|
1540
|
+
defaultAction,
|
|
1541
|
+
checkFresh,
|
|
1542
|
+
checkStaleReason,
|
|
1543
|
+
localHead: input.git.head,
|
|
1544
|
+
hostedHead: input.hosted?.sourceCommitSha ?? null
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
if (!input.git.isRepo) {
|
|
1548
|
+
return {
|
|
1549
|
+
state: "error",
|
|
1550
|
+
message: "Profile source is not Git-backed.",
|
|
1551
|
+
agentCount: input.agents.length,
|
|
1552
|
+
actionCount: input.actionCatalog.length,
|
|
1553
|
+
defaultAction,
|
|
1554
|
+
checkFresh: false,
|
|
1555
|
+
checkStaleReason: "Profile source is not Git-backed.",
|
|
1556
|
+
localHead: null,
|
|
1557
|
+
hostedHead: input.hosted?.sourceCommitSha ?? null
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
if (!input.git.head) {
|
|
1561
|
+
return {
|
|
1562
|
+
state: "pending_commit",
|
|
1563
|
+
message: "Profile source is ready for its first commit.",
|
|
1564
|
+
agentCount: input.agents.length,
|
|
1565
|
+
actionCount: input.actionCatalog.length,
|
|
1566
|
+
defaultAction,
|
|
1567
|
+
checkFresh: false,
|
|
1568
|
+
checkStaleReason: "Profile source has no committed head.",
|
|
1569
|
+
localHead: null,
|
|
1570
|
+
hostedHead: input.hosted?.sourceCommitSha ?? null
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
if (input.git.dirty) {
|
|
1574
|
+
return {
|
|
1575
|
+
state: "dirty",
|
|
1576
|
+
message: profileDirtyMessage(input.diff),
|
|
1577
|
+
agentCount: input.agents.length,
|
|
1578
|
+
actionCount: input.actionCatalog.length,
|
|
1579
|
+
defaultAction,
|
|
1580
|
+
checkFresh,
|
|
1581
|
+
checkStaleReason,
|
|
1582
|
+
localHead: input.git.head,
|
|
1583
|
+
hostedHead: input.hosted?.sourceCommitSha ?? null
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
return {
|
|
1587
|
+
state: "ready",
|
|
1588
|
+
message: input.catalog.stale ? "Profile source is clean; action catalog needs inspect/build." : "Profile source is clean.",
|
|
1589
|
+
agentCount: input.agents.length,
|
|
1590
|
+
actionCount: input.actionCatalog.length,
|
|
1591
|
+
defaultAction,
|
|
1592
|
+
checkFresh,
|
|
1593
|
+
checkStaleReason,
|
|
1594
|
+
localHead: input.git.head,
|
|
1595
|
+
hostedHead: input.hosted?.sourceCommitSha ?? null
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
function profileCheckStaleReason(input) {
|
|
1599
|
+
if (input.catalog.error) return `Catalog error: ${input.catalog.error}`;
|
|
1600
|
+
if (input.catalog.stale) return "Action catalog artifacts are missing or stale.";
|
|
1601
|
+
if (!input.lastCheck) return "Profile checks have not run.";
|
|
1602
|
+
if (input.lastCheck.status !== "passed") return `Last ${input.lastCheck.command} check failed.`;
|
|
1603
|
+
if (!input.lastCheck.sourceHead) return "Last check did not record a source head.";
|
|
1604
|
+
if (input.git.head && input.lastCheck.sourceHead !== input.git.head) {
|
|
1605
|
+
return "Profile source changed since the last check.";
|
|
1606
|
+
}
|
|
1607
|
+
if (input.git.dirty) return "Profile has uncommitted source changes.";
|
|
1608
|
+
return null;
|
|
1609
|
+
}
|
|
1610
|
+
function profileDirtyMessage(diff) {
|
|
1611
|
+
const parts = [];
|
|
1612
|
+
if (diff.changedAgents.length > 0) parts.push(`${diff.changedAgents.length} changed agent(s)`);
|
|
1613
|
+
if (diff.newAgents.length > 0) parts.push(`${diff.newAgents.length} new agent(s)`);
|
|
1614
|
+
if (diff.deletedAgents.length > 0) parts.push(`${diff.deletedAgents.length} deleted agent(s)`);
|
|
1615
|
+
if (diff.changedSkills.length > 0) parts.push(`${diff.changedSkills.length} changed skill(s)`);
|
|
1616
|
+
if (diff.changedActions.length > 0) parts.push(`${diff.changedActions.length} changed action(s)`);
|
|
1617
|
+
if (diff.changedExtensions.length > 0) parts.push(`${diff.changedExtensions.length} changed extension(s)`);
|
|
1618
|
+
if (diff.setupChanges.length > 0) parts.push(`${diff.setupChanges.length} setup change(s)`);
|
|
1619
|
+
if (diff.envRequirementChanges.length > 0) parts.push(`${diff.envRequirementChanges.length} env requirement change(s)`);
|
|
1620
|
+
return parts.length > 0 ? `Profile has ${parts.join(", ")}.` : "Profile has uncommitted source changes.";
|
|
1621
|
+
}
|
|
1622
|
+
function summarizeProfileDiff(git, profile) {
|
|
1623
|
+
const diff = emptyProfileDiffSummary();
|
|
1624
|
+
diff.files = git.files;
|
|
1625
|
+
const profileRoot = `profiles/${profile}/`;
|
|
1626
|
+
for (const file of git.files) {
|
|
1627
|
+
const normalized = file.path.replace(/\\/g, "/");
|
|
1628
|
+
if (normalized === PROFILE_REPO_MANIFEST || normalized === ".gitignore") {
|
|
1629
|
+
addUnique(diff.setupChanges, normalized);
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
if (!normalized.startsWith(profileRoot)) continue;
|
|
1633
|
+
const relative = normalized.slice(profileRoot.length);
|
|
1634
|
+
classifyProfileFileChange(diff, file, relative);
|
|
1635
|
+
}
|
|
1636
|
+
return diff;
|
|
1637
|
+
}
|
|
1638
|
+
function classifyProfileFileChange(diff, file, relativePath) {
|
|
1639
|
+
const firstSegment = relativePath.split("/")[0] ?? "";
|
|
1640
|
+
if (firstSegment === "agent") {
|
|
1641
|
+
addAgentChange(diff, DEFAULT_PROFILE_AGENT, file.category);
|
|
1642
|
+
} else if (firstSegment === "agents") {
|
|
1643
|
+
const agentId = relativePath.split("/")[1];
|
|
1644
|
+
if (agentId) addAgentChange(diff, agentId, file.category);
|
|
1645
|
+
} else if (firstSegment === "skills") {
|
|
1646
|
+
addUnique(diff.changedSkills, relativePath.split("/")[1] ?? relativePath);
|
|
1647
|
+
} else if (firstSegment === "actions") {
|
|
1648
|
+
addUnique(diff.changedActions, relativePath.split("/")[1] ?? relativePath);
|
|
1649
|
+
} else if (firstSegment === "extensions") {
|
|
1650
|
+
addUnique(diff.changedExtensions, relativePath.split("/")[1] ?? relativePath);
|
|
1651
|
+
} else if (firstSegment === "settings" || relativePath === "package.json" || relativePath === "openpond.yaml" || relativePath === "openpond.lock") {
|
|
1652
|
+
addUnique(diff.setupChanges, relativePath);
|
|
1653
|
+
}
|
|
1654
|
+
if (relativePath.toLowerCase().includes("env")) {
|
|
1655
|
+
addUnique(diff.envRequirementChanges, relativePath);
|
|
1656
|
+
}
|
|
1657
|
+
if (relativePath.startsWith(".openpond/")) {
|
|
1658
|
+
addUnique(diff.changedActions, "catalog");
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
function addAgentChange(diff, agentId, category) {
|
|
1662
|
+
if (category === "added" || category === "untracked") {
|
|
1663
|
+
addUnique(diff.newAgents, agentId);
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
if (category === "deleted") {
|
|
1667
|
+
addUnique(diff.deletedAgents, agentId);
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
addUnique(diff.changedAgents, agentId);
|
|
1671
|
+
}
|
|
1672
|
+
function addUnique(values, value) {
|
|
1673
|
+
if (!value || values.includes(value)) return;
|
|
1674
|
+
values.push(value);
|
|
1675
|
+
}
|
|
1676
|
+
function normalizeProfileName(value) {
|
|
1677
|
+
const trimmed = value?.trim();
|
|
1678
|
+
if (!trimmed) return DEFAULT_LOCAL_PROFILE;
|
|
1679
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(trimmed)) {
|
|
1680
|
+
throw new Error("profile name may only contain letters, numbers, dots, underscores, and hyphens");
|
|
1681
|
+
}
|
|
1682
|
+
return trimmed;
|
|
1683
|
+
}
|
|
1684
|
+
function defaultProfileManifest(profile, profilePath) {
|
|
1685
|
+
return {
|
|
1686
|
+
schema: "openpond.profileRepo.v1",
|
|
1687
|
+
defaultProfile: profile,
|
|
1688
|
+
profiles: {
|
|
1689
|
+
[profile]: {
|
|
1690
|
+
path: profilePath,
|
|
1691
|
+
defaultAgent: DEFAULT_PROFILE_AGENT,
|
|
1692
|
+
enabledAgents: [DEFAULT_PROFILE_AGENT]
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
async function readProfileManifest(repoPath) {
|
|
1698
|
+
const manifestPath = path5.join(repoPath, PROFILE_REPO_MANIFEST);
|
|
1699
|
+
const raw = await readFile4(manifestPath, "utf8");
|
|
1700
|
+
const parsed = JSON.parse(raw);
|
|
1701
|
+
if (parsed.schema !== "openpond.profileRepo.v1" || !parsed.profiles || typeof parsed.profiles !== "object") {
|
|
1702
|
+
throw new Error(`${manifestPath} is not an OpenPond profile repo manifest.`);
|
|
1703
|
+
}
|
|
1704
|
+
return parsed;
|
|
1705
|
+
}
|
|
1706
|
+
async function writeProfileManifest(repoPath, manifest) {
|
|
1707
|
+
await writeFile2(path5.join(repoPath, PROFILE_REPO_MANIFEST), `${JSON.stringify(manifest, null, 2)}
|
|
1708
|
+
`, "utf8");
|
|
1709
|
+
}
|
|
1710
|
+
function profileSourcePath(manifest, repoPath, profile) {
|
|
1711
|
+
const profileConfig = manifest.profiles[profile];
|
|
1712
|
+
if (!profileConfig) {
|
|
1713
|
+
throw new Error(`Profile "${profile}" was not found in ${path5.join(repoPath, PROFILE_REPO_MANIFEST)}.`);
|
|
1714
|
+
}
|
|
1715
|
+
const sourcePath = path5.resolve(repoPath, profileConfig.path);
|
|
1716
|
+
if (!existsSync4(sourcePath)) {
|
|
1717
|
+
throw new Error(`Profile source path does not exist: ${sourcePath}`);
|
|
1718
|
+
}
|
|
1719
|
+
return sourcePath;
|
|
1720
|
+
}
|
|
1721
|
+
function resolveAgentSdkRoot() {
|
|
1722
|
+
const packageJsonPath = require2.resolve("openpond-agent-sdk/package.json");
|
|
1723
|
+
return path5.dirname(packageJsonPath);
|
|
1724
|
+
}
|
|
1725
|
+
function resolveAgentSdkCliLaunch() {
|
|
1726
|
+
const sdkRoot = resolveAgentSdkRoot();
|
|
1727
|
+
const distCli = path5.join(sdkRoot, "dist", "cli.js");
|
|
1728
|
+
if (existsSync4(distCli)) return { command: process.execPath, args: [distCli] };
|
|
1729
|
+
const sourceCli = path5.join(sdkRoot, "src", "cli.ts");
|
|
1730
|
+
if (existsSync4(sourceCli)) {
|
|
1731
|
+
return { command: process.execPath, args: [resolveTsxCli(sdkRoot), sourceCli] };
|
|
1732
|
+
}
|
|
1733
|
+
throw new Error(`openpond-agent CLI was not found under ${sdkRoot}`);
|
|
1734
|
+
}
|
|
1735
|
+
function resolveTsxCli(packageRoot) {
|
|
1736
|
+
return createRequire(path5.join(packageRoot, "package.json")).resolve("tsx/cli");
|
|
1737
|
+
}
|
|
1738
|
+
async function spawnCommand(command, args, options = {}) {
|
|
1739
|
+
return new Promise((resolve, reject) => {
|
|
1740
|
+
const proc = spawn2(command, args, {
|
|
1741
|
+
cwd: options.cwd,
|
|
1742
|
+
stdio: options.inherit ? "inherit" : "pipe"
|
|
1743
|
+
});
|
|
1744
|
+
let stdout = "";
|
|
1745
|
+
let stderr = "";
|
|
1746
|
+
let stdoutTruncated = false;
|
|
1747
|
+
let stderrTruncated = false;
|
|
1748
|
+
let timedOut = false;
|
|
1749
|
+
const timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
|
|
1750
|
+
const maxOutputBytes = options.maxOutputBytes ?? 2 * 1024 * 1024;
|
|
1751
|
+
const timeout = timeoutMs > 0 ? setTimeout(() => {
|
|
1752
|
+
timedOut = true;
|
|
1753
|
+
proc.kill("SIGTERM");
|
|
1754
|
+
}, timeoutMs) : null;
|
|
1755
|
+
const forceKill = timeoutMs > 0 ? setTimeout(() => {
|
|
1756
|
+
if (timedOut && proc.exitCode === null) proc.kill("SIGKILL");
|
|
1757
|
+
}, timeoutMs + 5e3) : null;
|
|
1758
|
+
if (!options.inherit) {
|
|
1759
|
+
proc.stdout?.on("data", (chunk) => {
|
|
1760
|
+
const next = appendBoundedOutput(stdout, String(chunk), maxOutputBytes, stdoutTruncated);
|
|
1761
|
+
stdout = next.text;
|
|
1762
|
+
stdoutTruncated = next.truncated;
|
|
1763
|
+
});
|
|
1764
|
+
proc.stderr?.on("data", (chunk) => {
|
|
1765
|
+
const next = appendBoundedOutput(stderr, String(chunk), maxOutputBytes, stderrTruncated);
|
|
1766
|
+
stderr = next.text;
|
|
1767
|
+
stderrTruncated = next.truncated;
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
const clearTimers = () => {
|
|
1771
|
+
if (timeout) clearTimeout(timeout);
|
|
1772
|
+
if (forceKill) clearTimeout(forceKill);
|
|
1773
|
+
};
|
|
1774
|
+
proc.on("error", (error) => {
|
|
1775
|
+
clearTimers();
|
|
1776
|
+
reject(error);
|
|
1777
|
+
});
|
|
1778
|
+
proc.on("close", (code) => {
|
|
1779
|
+
clearTimers();
|
|
1780
|
+
resolve({ code, stdout, stderr, timedOut, stdoutTruncated, stderrTruncated });
|
|
1781
|
+
});
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
function appendBoundedOutput(current, chunk, maxBytes, alreadyTruncated) {
|
|
1785
|
+
if (alreadyTruncated) return { text: current, truncated: true };
|
|
1786
|
+
const combined = `${current}${chunk}`;
|
|
1787
|
+
if (Buffer.byteLength(combined, "utf8") <= maxBytes) {
|
|
1788
|
+
return { text: combined, truncated: false };
|
|
1789
|
+
}
|
|
1790
|
+
const text5 = `${Buffer.from(combined).subarray(0, Math.max(0, maxBytes)).toString("utf8")}
|
|
1791
|
+
[truncated after ${maxBytes} bytes]
|
|
1792
|
+
`;
|
|
1793
|
+
return { text: text5, truncated: true };
|
|
1794
|
+
}
|
|
1795
|
+
function commandFailureDetail(result, command) {
|
|
1796
|
+
const detail = result.stderr.trim() || result.stdout.trim();
|
|
1797
|
+
const notes = [
|
|
1798
|
+
result.timedOut ? `${command} timed out` : "",
|
|
1799
|
+
result.stdoutTruncated ? "stdout truncated" : "",
|
|
1800
|
+
result.stderrTruncated ? "stderr truncated" : ""
|
|
1801
|
+
].filter(Boolean);
|
|
1802
|
+
return [detail, ...notes].filter(Boolean).join("\n");
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
// ../../packages/cloud/src/profile/hosted-run-idempotency.ts
|
|
1806
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
1807
|
+
function buildHostedRunIdempotencyKey(input) {
|
|
1808
|
+
const explicit = input.explicitKey?.trim();
|
|
1809
|
+
if (explicit) return explicit;
|
|
1810
|
+
const sourceHead = input.sourceHead?.trim() || "unknown-source";
|
|
1811
|
+
const targetProjectId = input.targetProjectId?.trim() || "no-target-project";
|
|
1812
|
+
const inputHash = hashStableJson(input.input).slice(0, 16);
|
|
1813
|
+
const base = `profile-push-run:${input.localHead}:${sourceHead}:${input.runtimeAgentId}:${targetProjectId}:${inputHash}`;
|
|
1814
|
+
if (!input.retry) return base;
|
|
1815
|
+
return `${base}:retry:${(input.randomId ?? randomUUID)()}`;
|
|
1816
|
+
}
|
|
1817
|
+
function hashStableJson(value) {
|
|
1818
|
+
return createHash2("sha256").update(stableJsonStringify(value)).digest("hex");
|
|
1819
|
+
}
|
|
1820
|
+
function stableJsonStringify(value) {
|
|
1821
|
+
if (value === void 0) return "undefined";
|
|
1822
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
1823
|
+
if (Array.isArray(value)) {
|
|
1824
|
+
return `[${value.map((item) => stableJsonStringify(item)).join(",")}]`;
|
|
1825
|
+
}
|
|
1826
|
+
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJsonStringify(item)}`).join(",")}}`;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
export {
|
|
1830
|
+
formatOpenPondProfileSetupRequirement,
|
|
1831
|
+
collectProfileSourceUploadEntries,
|
|
1832
|
+
hostedSourceCheckStatusFromPayload,
|
|
1833
|
+
hostedPublishStatusFromPayload,
|
|
1834
|
+
hostedRunSummaryFromPayload,
|
|
1835
|
+
hostedRunStatusFromRunSummary,
|
|
1836
|
+
defaultLocalProfileRepoPath,
|
|
1837
|
+
initLocalProfileRepo,
|
|
1838
|
+
loadLocalProfileRepo,
|
|
1839
|
+
loadOpenPondProfileState,
|
|
1840
|
+
runProfileSdkCommand,
|
|
1841
|
+
runProfileCheck,
|
|
1842
|
+
commitActiveProfileChanges,
|
|
1843
|
+
saveProfilePushStatus,
|
|
1844
|
+
buildHostedRunIdempotencyKey
|
|
1845
|
+
};
|