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,1646 @@
|
|
|
1
|
+
import { createRequire as __openpondCreateRequire } from "node:module"; var require = __openpondCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
createSandboxFromPlan,
|
|
4
|
+
summarizeSandbox
|
|
5
|
+
} from "./chunk-AUJLPKGI.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveGitBranch
|
|
8
|
+
} from "./chunk-74O6FGVN.js";
|
|
9
|
+
import "./chunk-OG75GMVO.js";
|
|
10
|
+
import {
|
|
11
|
+
OPENPOND_MANIFEST_FILE_NAME,
|
|
12
|
+
SANDBOX_TEMPLATE_BUILD_PLAN_FILE_NAME,
|
|
13
|
+
SANDBOX_TEMPLATE_BUILD_PLAN_KIND,
|
|
14
|
+
formatSandboxTemplateDiagnostics,
|
|
15
|
+
sandboxTemplateBuildMetadata,
|
|
16
|
+
sandboxTemplateBuildPlan,
|
|
17
|
+
sandboxTemplateExecutableEntries,
|
|
18
|
+
sandboxTemplateJsonSchema,
|
|
19
|
+
sandboxTemplateScaffoldFiles,
|
|
20
|
+
validateSandboxTemplateYaml
|
|
21
|
+
} from "./chunk-BYAPLT37.js";
|
|
22
|
+
import "./chunk-WYDVQOBL.js";
|
|
23
|
+
import {
|
|
24
|
+
createRepo,
|
|
25
|
+
ensureApiKey,
|
|
26
|
+
formatTokenizedRepoUrl,
|
|
27
|
+
getGitRemoteUrl,
|
|
28
|
+
loadConfig,
|
|
29
|
+
parseSandboxRuntimePromotionPolicyOption,
|
|
30
|
+
parseSandboxTemplateEnvOptions,
|
|
31
|
+
parseSandboxWorkflowModeOption,
|
|
32
|
+
promptConfirm,
|
|
33
|
+
redactToken,
|
|
34
|
+
resolveRepoUrl,
|
|
35
|
+
resolveSandboxClient,
|
|
36
|
+
runCommand,
|
|
37
|
+
runShellCommand,
|
|
38
|
+
warnOnRepoHostMismatch
|
|
39
|
+
} from "./chunk-XSAXENQD.js";
|
|
40
|
+
import {
|
|
41
|
+
resolveBaseUrl,
|
|
42
|
+
resolvePublicApiBaseUrl
|
|
43
|
+
} from "./chunk-5G4E547X.js";
|
|
44
|
+
import {
|
|
45
|
+
parseBooleanOption,
|
|
46
|
+
parseIntegerOption,
|
|
47
|
+
parseJsonOption
|
|
48
|
+
} from "./chunk-3Y4EL3P6.js";
|
|
49
|
+
import "./chunk-UACZUFIA.js";
|
|
50
|
+
|
|
51
|
+
// src/cli/sandbox-template.ts
|
|
52
|
+
import { promises as fs3, existsSync as existsSync2 } from "node:fs";
|
|
53
|
+
import path4 from "node:path";
|
|
54
|
+
|
|
55
|
+
// src/cli/sandbox-template-local.ts
|
|
56
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
57
|
+
import { promises as fs2 } from "node:fs";
|
|
58
|
+
import path3 from "node:path";
|
|
59
|
+
|
|
60
|
+
// src/cli/sandbox-template-start.ts
|
|
61
|
+
import { Buffer } from "node:buffer";
|
|
62
|
+
import { promises as fs } from "node:fs";
|
|
63
|
+
import path2 from "node:path";
|
|
64
|
+
|
|
65
|
+
// src/cli/sandbox-template-schedules.ts
|
|
66
|
+
async function createSandboxTemplateStartSchedules(client, sandboxId, manifest, options) {
|
|
67
|
+
const selection = resolveSandboxTemplateStartScheduleSelection(
|
|
68
|
+
manifest,
|
|
69
|
+
options
|
|
70
|
+
);
|
|
71
|
+
if (!selection) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const overrides = parseSandboxTemplateScheduleOverrides(manifest, options);
|
|
75
|
+
const projectId = typeof options.projectId === "string" && options.projectId.trim() ? options.projectId.trim() : typeof options.runtimeProjectId === "string" ? options.runtimeProjectId.trim() : "";
|
|
76
|
+
const teamId = typeof options.teamId === "string" ? options.teamId.trim() : "";
|
|
77
|
+
const created = [];
|
|
78
|
+
for (const schedule of selection.schedules) {
|
|
79
|
+
const input2 = buildSandboxTemplateStartScheduleInput({
|
|
80
|
+
manifest,
|
|
81
|
+
schedule,
|
|
82
|
+
override: overrides.get(schedule.name),
|
|
83
|
+
mode: selection.mode,
|
|
84
|
+
sourceSandboxId: sandboxId,
|
|
85
|
+
teamId,
|
|
86
|
+
projectId
|
|
87
|
+
});
|
|
88
|
+
const result = await client.createSchedule(input2);
|
|
89
|
+
created.push({
|
|
90
|
+
id: result.schedule.id,
|
|
91
|
+
name: result.schedule.name,
|
|
92
|
+
enabled: result.schedule.enabled,
|
|
93
|
+
scheduleType: result.schedule.scheduleType,
|
|
94
|
+
scheduleExpression: result.schedule.scheduleExpression,
|
|
95
|
+
syncStatus: result.schedule.syncStatus,
|
|
96
|
+
awsScheduleArn: result.schedule.awsScheduleArn ?? null,
|
|
97
|
+
target: result.schedule.target
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return created;
|
|
101
|
+
}
|
|
102
|
+
function resolveSandboxTemplateStartScheduleSelection(manifest, options) {
|
|
103
|
+
const enableRaw = options.enableSchedules ?? options.enableSchedule;
|
|
104
|
+
const disableRaw = options.disableSchedules ?? options.disableSchedule;
|
|
105
|
+
if (enableRaw !== void 0 && disableRaw !== void 0) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
"pass only one of --enable-schedules or --disable-schedules"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
let mode = null;
|
|
111
|
+
let rawSelection;
|
|
112
|
+
if (enableRaw !== void 0) {
|
|
113
|
+
mode = "enabled";
|
|
114
|
+
rawSelection = enableRaw;
|
|
115
|
+
} else if (disableRaw !== void 0) {
|
|
116
|
+
mode = "disabled";
|
|
117
|
+
rawSelection = disableRaw;
|
|
118
|
+
} else if (options.schedules !== void 0) {
|
|
119
|
+
rawSelection = options.schedules;
|
|
120
|
+
const rawMode = typeof options.scheduleMode === "string" ? options.scheduleMode.trim().toLowerCase() : "disabled";
|
|
121
|
+
if (rawMode === "none") {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
if (rawMode !== "enabled" && rawMode !== "disabled") {
|
|
125
|
+
throw new Error("--schedule-mode must be enabled, disabled, or none");
|
|
126
|
+
}
|
|
127
|
+
mode = rawMode;
|
|
128
|
+
}
|
|
129
|
+
if (!mode) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
if (manifest.schedules.length === 0) {
|
|
133
|
+
return { mode, schedules: [] };
|
|
134
|
+
}
|
|
135
|
+
const selectedNames = parseSandboxTemplateScheduleNameSelection(
|
|
136
|
+
rawSelection,
|
|
137
|
+
manifest
|
|
138
|
+
);
|
|
139
|
+
const schedules = selectedNames === null ? manifest.schedules : manifest.schedules.filter(
|
|
140
|
+
(schedule) => selectedNames.has(schedule.name)
|
|
141
|
+
);
|
|
142
|
+
return { mode, schedules };
|
|
143
|
+
}
|
|
144
|
+
function parseSandboxTemplateScheduleNameSelection(value, manifest) {
|
|
145
|
+
if (value === void 0 || value === true) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const raw = String(value).trim();
|
|
149
|
+
if (!raw || raw === "true" || raw.toLowerCase() === "all") {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
if (raw.toLowerCase() === "none") {
|
|
153
|
+
return /* @__PURE__ */ new Set();
|
|
154
|
+
}
|
|
155
|
+
const known = new Set(manifest.schedules.map((schedule) => schedule.name));
|
|
156
|
+
const selected = new Set(
|
|
157
|
+
raw.split(",").map((item) => item.trim()).filter(Boolean)
|
|
158
|
+
);
|
|
159
|
+
for (const name of selected) {
|
|
160
|
+
if (!known.has(name)) {
|
|
161
|
+
throw new Error(`manifest schedule not found: ${name}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return selected;
|
|
165
|
+
}
|
|
166
|
+
function parseSandboxTemplateScheduleOverrides(manifest, options) {
|
|
167
|
+
const raw = typeof options.scheduleOverrides === "string" ? options.scheduleOverrides : typeof options.scheduleOverride === "string" ? options.scheduleOverride : "";
|
|
168
|
+
if (!raw.trim()) {
|
|
169
|
+
return /* @__PURE__ */ new Map();
|
|
170
|
+
}
|
|
171
|
+
const parsed = parseJsonOption(raw, "schedule-overrides");
|
|
172
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
"schedule-overrides must be a JSON object keyed by schedule name"
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const known = new Set(manifest.schedules.map((schedule) => schedule.name));
|
|
178
|
+
const out = /* @__PURE__ */ new Map();
|
|
179
|
+
for (const [name, override] of Object.entries(parsed)) {
|
|
180
|
+
if (!known.has(name)) {
|
|
181
|
+
throw new Error(`schedule override target does not exist: ${name}`);
|
|
182
|
+
}
|
|
183
|
+
if (!override || typeof override !== "object" || Array.isArray(override)) {
|
|
184
|
+
throw new Error(`schedule override for ${name} must be an object`);
|
|
185
|
+
}
|
|
186
|
+
out.set(name, override);
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
function buildSandboxTemplateStartScheduleInput(params) {
|
|
191
|
+
const schedule = mergeSandboxTemplateScheduleOverride(
|
|
192
|
+
params.schedule,
|
|
193
|
+
params.override
|
|
194
|
+
);
|
|
195
|
+
const expression = sandboxTemplateScheduleExpression(schedule);
|
|
196
|
+
return {
|
|
197
|
+
sourceSandboxId: params.sourceSandboxId,
|
|
198
|
+
...params.teamId ? { teamId: params.teamId } : {},
|
|
199
|
+
...params.projectId ? { projectId: params.projectId } : {},
|
|
200
|
+
name: schedule.name,
|
|
201
|
+
...schedule.description ? { description: schedule.description } : {},
|
|
202
|
+
...expression,
|
|
203
|
+
...schedule.timezone ? { timezone: schedule.timezone } : {},
|
|
204
|
+
enabled: typeof params.override?.enabled === "boolean" ? params.override.enabled : params.mode === "enabled",
|
|
205
|
+
...schedule.startAt ? { startAt: schedule.startAt } : {},
|
|
206
|
+
...schedule.endAt ? { endAt: schedule.endAt } : {},
|
|
207
|
+
...schedule.maxRuns !== void 0 ? { maxRuns: schedule.maxRuns } : {},
|
|
208
|
+
runtimePolicy: schedule.runtimePolicy,
|
|
209
|
+
target: sandboxTemplateScheduleCommandTarget(params.manifest, schedule),
|
|
210
|
+
...schedule.budget ? { budget: schedule.budget } : {},
|
|
211
|
+
...schedule.resources ? { resources: schedule.resources } : {},
|
|
212
|
+
...schedule.quotas ? { quotas: schedule.quotas } : {},
|
|
213
|
+
...schedule.lifecycle ? { lifecycle: schedule.lifecycle } : {},
|
|
214
|
+
...schedule.retentionPolicy ? { retentionPolicy: schedule.retentionPolicy } : {},
|
|
215
|
+
...schedule.env ? { env: schedule.env } : {},
|
|
216
|
+
...schedule.integrationLeases ? {
|
|
217
|
+
integrationLeases: schedule.integrationLeases
|
|
218
|
+
} : {},
|
|
219
|
+
metadata: {
|
|
220
|
+
...schedule.metadata ?? {},
|
|
221
|
+
manifestScheduleName: schedule.name,
|
|
222
|
+
source: "openpond-code-sandbox-template-start"
|
|
223
|
+
},
|
|
224
|
+
managementSource: "openpond.yaml",
|
|
225
|
+
manifestPath: OPENPOND_MANIFEST_FILE_NAME
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function mergeSandboxTemplateScheduleOverride(schedule, override) {
|
|
229
|
+
if (!override) {
|
|
230
|
+
return schedule;
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
...schedule,
|
|
234
|
+
...override,
|
|
235
|
+
target: schedule.target || override.target ? {
|
|
236
|
+
...schedule.target ?? {},
|
|
237
|
+
...override.target ?? {}
|
|
238
|
+
} : void 0,
|
|
239
|
+
metadata: schedule.metadata || override.metadata ? {
|
|
240
|
+
...schedule.metadata ?? {},
|
|
241
|
+
...override.metadata ?? {}
|
|
242
|
+
} : void 0
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function sandboxTemplateScheduleExpression(schedule) {
|
|
246
|
+
if (schedule.scheduleExpression) {
|
|
247
|
+
return {
|
|
248
|
+
scheduleType: schedule.scheduleType ?? "cron",
|
|
249
|
+
scheduleExpression: schedule.scheduleExpression
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
if (schedule.rate) {
|
|
253
|
+
return {
|
|
254
|
+
scheduleType: "rate",
|
|
255
|
+
scheduleExpression: /^rate\(/i.test(schedule.rate) ? schedule.rate : `rate(${schedule.rate})`
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
if (schedule.once) {
|
|
259
|
+
return {
|
|
260
|
+
scheduleType: "once",
|
|
261
|
+
scheduleExpression: /^at\(/i.test(schedule.once) ? schedule.once : `at(${schedule.once})`
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
scheduleType: "cron",
|
|
266
|
+
scheduleExpression: schedule.cron ?? ""
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function sandboxTemplateScheduleCommandTarget(manifest, schedule) {
|
|
270
|
+
const explicitCommand = schedule.command ?? schedule.target?.command ?? null;
|
|
271
|
+
if (explicitCommand) {
|
|
272
|
+
return {
|
|
273
|
+
kind: "command",
|
|
274
|
+
command: explicitCommand,
|
|
275
|
+
requiresStart: schedule.requiresStart ?? schedule.target?.requiresStart ?? false
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const targetKind = schedule.target?.kind ?? "action";
|
|
279
|
+
if (targetKind === "start") {
|
|
280
|
+
return {
|
|
281
|
+
kind: "command",
|
|
282
|
+
command: manifest.start.command,
|
|
283
|
+
requiresStart: schedule.requiresStart ?? schedule.target?.requiresStart ?? manifest.start.requiresStart ?? false
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
if (targetKind === "service") {
|
|
287
|
+
const serviceName = schedule.target?.name ?? "";
|
|
288
|
+
const service = manifest.services.find((item) => item.name === serviceName);
|
|
289
|
+
if (!service) {
|
|
290
|
+
throw new Error(`schedule service target does not exist: ${serviceName}`);
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
kind: "command",
|
|
294
|
+
command: service.command,
|
|
295
|
+
requiresStart: schedule.requiresStart ?? schedule.target?.requiresStart ?? service.requiresStart ?? false
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const actionName = schedule.actionName ?? schedule.action ?? schedule.target?.actionName ?? (schedule.target?.kind === "action" ? schedule.target?.name : void 0);
|
|
299
|
+
const action = manifest.actions.find((item) => item.name === actionName);
|
|
300
|
+
if (!action) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`schedule action target does not exist: ${actionName ?? ""}`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
kind: "command",
|
|
307
|
+
command: action.command,
|
|
308
|
+
requiresStart: schedule.requiresStart ?? schedule.target?.requiresStart ?? action.requiresStart ?? false
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/cli/sandbox-template-git.ts
|
|
313
|
+
import { existsSync } from "node:fs";
|
|
314
|
+
import path from "node:path";
|
|
315
|
+
import { stdin as input } from "node:process";
|
|
316
|
+
async function resolveSandboxTemplateStartRepo(manifest, options, projectPath) {
|
|
317
|
+
const explicitRepo = typeof options.repo === "string" ? options.repo.trim() : "";
|
|
318
|
+
if (explicitRepo) {
|
|
319
|
+
return normalizeSandboxTemplateRepoUrl(explicitRepo);
|
|
320
|
+
}
|
|
321
|
+
await ensureGitRepository(projectPath);
|
|
322
|
+
const config = await loadConfig();
|
|
323
|
+
const uiBase = resolveBaseUrl(config);
|
|
324
|
+
const apiBase = resolvePublicApiBaseUrl(config);
|
|
325
|
+
const apiKey = await ensureApiKey(config, uiBase);
|
|
326
|
+
let originUrl = await getGitRemoteUrl(projectPath, "origin");
|
|
327
|
+
if (!originUrl) {
|
|
328
|
+
const teamId = typeof options.teamId === "string" ? options.teamId.trim() : "";
|
|
329
|
+
const response = await createRepo(apiBase, apiKey, {
|
|
330
|
+
name: manifest.name,
|
|
331
|
+
description: manifest.description,
|
|
332
|
+
repoInit: "empty",
|
|
333
|
+
...teamId ? { teamId } : {}
|
|
334
|
+
});
|
|
335
|
+
originUrl = resolveRepoUrl(response);
|
|
336
|
+
const remoteResult = await runCommand(
|
|
337
|
+
"git",
|
|
338
|
+
["remote", "add", "origin", originUrl],
|
|
339
|
+
{
|
|
340
|
+
cwd: projectPath
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
if (remoteResult.code !== 0) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`git remote add failed: ${remoteResult.stderr.trim() || remoteResult.stdout.trim() || "unknown error"}`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
warnOnRepoHostMismatch(originUrl);
|
|
350
|
+
await ensureGitCommitForSandboxTemplateStart(projectPath, options);
|
|
351
|
+
if (!parseBooleanOption(options.noPush)) {
|
|
352
|
+
const branch = await resolveSandboxTemplateStartBranch(
|
|
353
|
+
projectPath,
|
|
354
|
+
options
|
|
355
|
+
);
|
|
356
|
+
await pushGitBranchForSandboxTemplateStart(
|
|
357
|
+
projectPath,
|
|
358
|
+
originUrl,
|
|
359
|
+
branch,
|
|
360
|
+
apiKey,
|
|
361
|
+
options
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
return normalizeSandboxTemplateRepoUrl(originUrl);
|
|
365
|
+
}
|
|
366
|
+
async function ensureGitRepository(projectPath) {
|
|
367
|
+
const gitDir = path.join(projectPath, ".git");
|
|
368
|
+
if (existsSync(gitDir)) return;
|
|
369
|
+
const result = await runCommand("git", ["init"], { cwd: projectPath });
|
|
370
|
+
if (result.code !== 0) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
`git init failed: ${result.stderr.trim() || result.stdout.trim() || "unknown error"}`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async function ensureGitCommitForSandboxTemplateStart(projectPath, options) {
|
|
377
|
+
const status = await runCommand("git", ["status", "--porcelain"], {
|
|
378
|
+
cwd: projectPath
|
|
379
|
+
});
|
|
380
|
+
if (status.code !== 0) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
`git status failed: ${status.stderr.trim() || status.stdout.trim() || "unknown error"}`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
const head = await runCommand("git", ["rev-parse", "--verify", "HEAD"], {
|
|
386
|
+
cwd: projectPath
|
|
387
|
+
});
|
|
388
|
+
const hasHead = head.code === 0;
|
|
389
|
+
const dirty = status.stdout.trim().length > 0;
|
|
390
|
+
if (!dirty && hasHead) return;
|
|
391
|
+
const shouldCommit = parseBooleanOption(options.commit) || parseBooleanOption(options.yes) || (input.isTTY ? await promptConfirm(
|
|
392
|
+
hasHead ? "Commit local changes before starting the sandbox?" : "Create the initial git commit before starting the sandbox?",
|
|
393
|
+
!hasHead
|
|
394
|
+
) : false);
|
|
395
|
+
if (!shouldCommit) {
|
|
396
|
+
if (!hasHead) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
"local repository has no commits; pass --commit or commit before starting"
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if (dirty) {
|
|
402
|
+
console.warn(
|
|
403
|
+
"warning: uncommitted local changes will not be included in the sandbox"
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const add = await runCommand("git", ["add", "-A"], { cwd: projectPath });
|
|
409
|
+
if (add.code !== 0) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`git add failed: ${add.stderr.trim() || add.stdout.trim() || "unknown error"}`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
const message = typeof options.commitMessage === "string" && options.commitMessage.trim() ? options.commitMessage.trim() : `start ${OPENPOND_MANIFEST_FILE_NAME} sandbox`;
|
|
415
|
+
const commit = await runCommand("git", ["commit", "-m", message], {
|
|
416
|
+
cwd: projectPath
|
|
417
|
+
});
|
|
418
|
+
if (commit.code !== 0) {
|
|
419
|
+
const output = commit.stderr.trim() || commit.stdout.trim();
|
|
420
|
+
if (output.includes("nothing to commit") && hasHead) return;
|
|
421
|
+
throw new Error(`git commit failed: ${output || "unknown error"}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function resolveSandboxTemplateStartBranch(projectPath, options) {
|
|
425
|
+
const branchOption = typeof options.branch === "string" ? options.branch.trim() : "";
|
|
426
|
+
const branch = branchOption || await resolveGitBranch(projectPath);
|
|
427
|
+
if (!branch) {
|
|
428
|
+
throw new Error("unable to resolve git branch; pass --branch");
|
|
429
|
+
}
|
|
430
|
+
return branch;
|
|
431
|
+
}
|
|
432
|
+
async function pushGitBranchForSandboxTemplateStart(projectPath, originUrl, branch, apiKey, options) {
|
|
433
|
+
let tokenRemote;
|
|
434
|
+
try {
|
|
435
|
+
tokenRemote = formatTokenizedRepoUrl(originUrl, apiKey);
|
|
436
|
+
} catch {
|
|
437
|
+
throw new Error("origin remote must be https for tokenized pushes");
|
|
438
|
+
}
|
|
439
|
+
const keepTokenRemote = parseBooleanOption(options.keepTokenRemote) || parseBooleanOption(options.token) || parseBooleanOption(options.setRemoteToken);
|
|
440
|
+
const alreadyTokenized = originUrl.includes("x-access-token:");
|
|
441
|
+
const restoreUrl = !keepTokenRemote && !alreadyTokenized ? originUrl : null;
|
|
442
|
+
const previousPrompt = process.env.GIT_TERMINAL_PROMPT;
|
|
443
|
+
process.env.GIT_TERMINAL_PROMPT = "0";
|
|
444
|
+
try {
|
|
445
|
+
if (!alreadyTokenized) {
|
|
446
|
+
const setResult = await runCommand(
|
|
447
|
+
"git",
|
|
448
|
+
["remote", "set-url", "origin", tokenRemote],
|
|
449
|
+
{
|
|
450
|
+
cwd: projectPath
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
if (setResult.code !== 0) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`git remote set-url failed: ${redactToken(
|
|
456
|
+
setResult.stderr.trim() || setResult.stdout.trim() || "unknown error"
|
|
457
|
+
)}`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
const push = await runCommand("git", ["push", "-u", "origin", branch], {
|
|
462
|
+
cwd: projectPath,
|
|
463
|
+
inherit: true
|
|
464
|
+
});
|
|
465
|
+
if (push.code !== 0) {
|
|
466
|
+
throw new Error("git push failed");
|
|
467
|
+
}
|
|
468
|
+
} finally {
|
|
469
|
+
if (restoreUrl) {
|
|
470
|
+
await runCommand("git", ["remote", "set-url", "origin", restoreUrl], {
|
|
471
|
+
cwd: projectPath
|
|
472
|
+
}).catch(() => null);
|
|
473
|
+
}
|
|
474
|
+
if (previousPrompt === void 0) {
|
|
475
|
+
delete process.env.GIT_TERMINAL_PROMPT;
|
|
476
|
+
} else {
|
|
477
|
+
process.env.GIT_TERMINAL_PROMPT = previousPrompt;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function normalizeSandboxTemplateRepoIdentity(repoUrl) {
|
|
482
|
+
return normalizeSandboxTemplateRepoUrl(repoUrl).replace(/\.git$/, "");
|
|
483
|
+
}
|
|
484
|
+
function normalizeSandboxTemplateRepoUrl(repoUrl) {
|
|
485
|
+
const parsed = new URL(repoUrl);
|
|
486
|
+
parsed.username = "";
|
|
487
|
+
parsed.password = "";
|
|
488
|
+
const text = parsed.toString();
|
|
489
|
+
return text.endsWith(".git") ? text : `${text.replace(/\/$/, "")}.git`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/cli/sandbox-template-start.ts
|
|
493
|
+
async function runSandboxTemplateStart(options) {
|
|
494
|
+
const filePath = resolveSandboxTemplateFilePath(options);
|
|
495
|
+
const projectPath = path2.dirname(filePath);
|
|
496
|
+
const source = await fs.readFile(filePath, "utf8");
|
|
497
|
+
const result = validateSandboxTemplateYaml(source);
|
|
498
|
+
if (!result.ok) {
|
|
499
|
+
console.error(formatSandboxTemplateDiagnostics(result.diagnostics));
|
|
500
|
+
process.exitCode = 1;
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const manifest = result.manifest;
|
|
504
|
+
const executable = resolveSandboxTemplateStartExecutable(manifest, options);
|
|
505
|
+
const input2 = await resolveSandboxTemplateStartInput(
|
|
506
|
+
manifest,
|
|
507
|
+
options,
|
|
508
|
+
projectPath
|
|
509
|
+
);
|
|
510
|
+
const repo = await resolveSandboxTemplateStartRepo(
|
|
511
|
+
manifest,
|
|
512
|
+
options,
|
|
513
|
+
projectPath
|
|
514
|
+
);
|
|
515
|
+
const client = await resolveSandboxClient(options);
|
|
516
|
+
const createInput = buildSandboxTemplateStartCreateInput(
|
|
517
|
+
manifest,
|
|
518
|
+
options,
|
|
519
|
+
repo
|
|
520
|
+
);
|
|
521
|
+
const sandbox = await createSandboxTemplateStartSandbox(
|
|
522
|
+
client,
|
|
523
|
+
createInput,
|
|
524
|
+
repo
|
|
525
|
+
);
|
|
526
|
+
await waitForSandboxTemplateRunnerReady(client, sandbox.id);
|
|
527
|
+
const setupCommands = await runSandboxTemplateSetupCommands(
|
|
528
|
+
client,
|
|
529
|
+
sandbox.id,
|
|
530
|
+
manifest,
|
|
531
|
+
options
|
|
532
|
+
);
|
|
533
|
+
const uploadedFiles = await uploadSandboxTemplateStartFiles(
|
|
534
|
+
client,
|
|
535
|
+
sandbox.id,
|
|
536
|
+
input2.uploadRequests
|
|
537
|
+
);
|
|
538
|
+
const commandInput = {
|
|
539
|
+
...input2.scalars,
|
|
540
|
+
...formatUploadedFileParams(uploadedFiles, input2.uploadRequests)
|
|
541
|
+
};
|
|
542
|
+
const execution = await runSandboxTemplateExecutable(
|
|
543
|
+
client,
|
|
544
|
+
sandbox.id,
|
|
545
|
+
executable,
|
|
546
|
+
commandInput,
|
|
547
|
+
sandboxTemplateRuntimeEnv(sandbox)
|
|
548
|
+
);
|
|
549
|
+
const previews = await openSandboxTemplatePorts(
|
|
550
|
+
client,
|
|
551
|
+
sandbox.id,
|
|
552
|
+
executable.ports
|
|
553
|
+
);
|
|
554
|
+
const schedules = await createSandboxTemplateStartSchedules(
|
|
555
|
+
client,
|
|
556
|
+
sandbox.id,
|
|
557
|
+
manifest,
|
|
558
|
+
options
|
|
559
|
+
);
|
|
560
|
+
console.log(
|
|
561
|
+
JSON.stringify(
|
|
562
|
+
{
|
|
563
|
+
ok: true,
|
|
564
|
+
file: filePath,
|
|
565
|
+
repo,
|
|
566
|
+
executable: {
|
|
567
|
+
kind: executable.kind,
|
|
568
|
+
name: executable.name,
|
|
569
|
+
command: executable.command
|
|
570
|
+
},
|
|
571
|
+
sandbox: summarizeSandbox(sandbox),
|
|
572
|
+
setupCommands,
|
|
573
|
+
uploadedFiles,
|
|
574
|
+
input: commandInput,
|
|
575
|
+
execution,
|
|
576
|
+
previews,
|
|
577
|
+
schedules,
|
|
578
|
+
expectedArtifacts: executable.artifactPaths
|
|
579
|
+
},
|
|
580
|
+
null,
|
|
581
|
+
2
|
|
582
|
+
)
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
function resolveSandboxTemplateStartExecutable(manifest, options) {
|
|
586
|
+
const executables = sandboxTemplateExecutableEntries(manifest);
|
|
587
|
+
const action = typeof options.action === "string" ? options.action.trim() : "";
|
|
588
|
+
const service = typeof options.service === "string" ? options.service.trim() : "";
|
|
589
|
+
const target = typeof options.target === "string" ? options.target.trim() : "";
|
|
590
|
+
const entrypoint = typeof options.entrypoint === "string" ? options.entrypoint.trim() : "";
|
|
591
|
+
const requested = action || service || target || entrypoint || "start";
|
|
592
|
+
const kind = action ? "action" : service ? "service" : "";
|
|
593
|
+
const match = executables.find(
|
|
594
|
+
(candidate) => candidate.name === requested && (!kind || candidate.kind === kind)
|
|
595
|
+
);
|
|
596
|
+
if (!match) {
|
|
597
|
+
throw new Error(
|
|
598
|
+
`manifest executable not found: ${requested}. Available: ${executables.map((candidate) => `${candidate.kind}:${candidate.name}`).join(", ")}`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
return match;
|
|
602
|
+
}
|
|
603
|
+
function buildSandboxTemplateStartCreateInput(manifest, options, repo) {
|
|
604
|
+
const budgetUsd = typeof options.budgetUsd === "string" && options.budgetUsd.trim() ? options.budgetUsd.trim() : typeof options.budget === "string" && options.budget.trim() ? options.budget.trim() : "0.05";
|
|
605
|
+
const maxDurationSeconds = parseIntegerOption(
|
|
606
|
+
options.maxDurationSeconds,
|
|
607
|
+
"max-duration-seconds"
|
|
608
|
+
);
|
|
609
|
+
const idleTimeoutSeconds = parseIntegerOption(
|
|
610
|
+
options.idleTimeoutSeconds,
|
|
611
|
+
"idle-timeout-seconds"
|
|
612
|
+
);
|
|
613
|
+
const teamId = typeof options.teamId === "string" ? options.teamId.trim() : "";
|
|
614
|
+
const requestedProjectId = typeof options.projectId === "string" ? options.projectId.trim() : "";
|
|
615
|
+
const sandboxRuntimeProjectId = typeof options.runtimeProjectId === "string" ? options.runtimeProjectId.trim() : "";
|
|
616
|
+
if (requestedProjectId && sandboxRuntimeProjectId && requestedProjectId !== sandboxRuntimeProjectId) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
"project-id and runtime-project-id must match when both are set"
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
const projectId = requestedProjectId || sandboxRuntimeProjectId;
|
|
622
|
+
const requestedAgentId = typeof options.agentId === "string" ? options.agentId.trim() : "";
|
|
623
|
+
const sandboxRuntimeAgentId = typeof options.runtimeAgentId === "string" ? options.runtimeAgentId.trim() : "";
|
|
624
|
+
if (requestedAgentId && sandboxRuntimeAgentId && requestedAgentId !== sandboxRuntimeAgentId) {
|
|
625
|
+
throw new Error(
|
|
626
|
+
"agent-id and runtime-agent-id must match when both are set"
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
const agentId = requestedAgentId || sandboxRuntimeAgentId;
|
|
630
|
+
const sandboxWorkflowMode = parseSandboxWorkflowModeOption(options.workflowMode);
|
|
631
|
+
const sandboxRuntimePromotionPolicy = parseSandboxRuntimePromotionPolicyOption(options.runtimePromotionPolicy);
|
|
632
|
+
const sandboxRuntimeBaseBranch = typeof options.runtimeBaseBranch === "string" && options.runtimeBaseBranch.trim() ? options.runtimeBaseBranch.trim() : "";
|
|
633
|
+
const sandboxRuntimeBaseSha = typeof options.runtimeBaseSha === "string" && options.runtimeBaseSha.trim() ? options.runtimeBaseSha.trim() : "";
|
|
634
|
+
const runtimeId = typeof options.runtimeId === "string" && options.runtimeId.trim() ? options.runtimeId.trim() : "";
|
|
635
|
+
const sandboxRuntimeRequested = Boolean(
|
|
636
|
+
sandboxWorkflowMode || sandboxRuntimePromotionPolicy || sandboxRuntimeBaseBranch || sandboxRuntimeBaseSha || runtimeId || sandboxRuntimeProjectId || sandboxRuntimeAgentId
|
|
637
|
+
);
|
|
638
|
+
const env = parseSandboxTemplateEnvOptions(manifest, options);
|
|
639
|
+
const workloadSource = sandboxWorkloadSourceFromManifest(manifest);
|
|
640
|
+
const sandbox = {
|
|
641
|
+
repo,
|
|
642
|
+
...teamId ? { teamId } : {},
|
|
643
|
+
...projectId ? { projectId } : {},
|
|
644
|
+
...agentId ? { agentId } : {},
|
|
645
|
+
resources: manifest.resources ?? {},
|
|
646
|
+
networkPolicy: {
|
|
647
|
+
internetEgress: sandboxTemplateInternetEgressPolicy(
|
|
648
|
+
manifest.network.egress
|
|
649
|
+
)
|
|
650
|
+
},
|
|
651
|
+
budget: { maxUsd: budgetUsd },
|
|
652
|
+
quotas: {
|
|
653
|
+
maxSpendUsd: budgetUsd,
|
|
654
|
+
...maxDurationSeconds !== void 0 ? { maxDurationSeconds } : {},
|
|
655
|
+
...idleTimeoutSeconds !== void 0 ? { idleTimeoutSeconds } : {}
|
|
656
|
+
},
|
|
657
|
+
...env.length > 0 ? { env } : {},
|
|
658
|
+
...workloadSource ? { workloadSource } : {},
|
|
659
|
+
volumes: manifest.volumes,
|
|
660
|
+
metadata: {
|
|
661
|
+
source: "openpond-code-sandbox-template-start",
|
|
662
|
+
manifestFile: OPENPOND_MANIFEST_FILE_NAME,
|
|
663
|
+
template: {
|
|
664
|
+
name: manifest.name,
|
|
665
|
+
version: manifest.version,
|
|
666
|
+
useCase: manifest.useCase
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
return {
|
|
671
|
+
sandbox,
|
|
672
|
+
...sandboxRuntimeRequested && runtimeId ? { runtimeId } : {},
|
|
673
|
+
...sandboxRuntimeRequested && !runtimeId ? {
|
|
674
|
+
sandboxRuntime: {
|
|
675
|
+
...teamId ? { teamId } : {},
|
|
676
|
+
...sandboxWorkflowMode ? { workflowMode: sandboxWorkflowMode } : {},
|
|
677
|
+
...projectId ? { projectId } : {},
|
|
678
|
+
...agentId ? { agentId } : {},
|
|
679
|
+
baseBranch: sandboxRuntimeBaseBranch || "master",
|
|
680
|
+
...sandboxRuntimeBaseSha ? { baseSha: sandboxRuntimeBaseSha } : {},
|
|
681
|
+
...sandboxRuntimePromotionPolicy ? { promotionPolicy: sandboxRuntimePromotionPolicy } : {}
|
|
682
|
+
}
|
|
683
|
+
} : {}
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function sandboxWorkloadSourceFromManifest(manifest) {
|
|
687
|
+
if (manifest.runtime.image) {
|
|
688
|
+
return { image: manifest.runtime.image };
|
|
689
|
+
}
|
|
690
|
+
if (manifest.runtime.dockerfile) {
|
|
691
|
+
return { dockerfile: manifest.runtime.dockerfile };
|
|
692
|
+
}
|
|
693
|
+
return void 0;
|
|
694
|
+
}
|
|
695
|
+
function sandboxTemplateInternetEgressPolicy(egress) {
|
|
696
|
+
return egress === "allow" ? "allow" : "block";
|
|
697
|
+
}
|
|
698
|
+
async function createSandboxTemplateStartSandbox(client, plan, repo) {
|
|
699
|
+
const requestedAt = Date.now();
|
|
700
|
+
try {
|
|
701
|
+
return (await createSandboxFromPlan(client, plan)).sandbox;
|
|
702
|
+
} catch (error) {
|
|
703
|
+
if (!isLikelySandboxCreateTimeout(error)) {
|
|
704
|
+
throw error;
|
|
705
|
+
}
|
|
706
|
+
console.warn(
|
|
707
|
+
"warning: sandbox create timed out; checking for the created sandbox record"
|
|
708
|
+
);
|
|
709
|
+
return recoverTimedOutSandboxCreate(
|
|
710
|
+
client,
|
|
711
|
+
plan.sandbox,
|
|
712
|
+
repo,
|
|
713
|
+
requestedAt
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function isLikelySandboxCreateTimeout(error) {
|
|
718
|
+
return error instanceof Error && /\b(504|timed out|timeout)\b/i.test(error.message);
|
|
719
|
+
}
|
|
720
|
+
async function recoverTimedOutSandboxCreate(client, input2, repo, requestedAt) {
|
|
721
|
+
const timeoutMs = 12 * 6e4;
|
|
722
|
+
const pollMs = 3e3;
|
|
723
|
+
const deadline = Date.now() + timeoutMs;
|
|
724
|
+
const repoIdentity = normalizeSandboxTemplateRepoIdentity(repo);
|
|
725
|
+
const metadata = input2.metadata ?? {};
|
|
726
|
+
while (Date.now() < deadline) {
|
|
727
|
+
const sandboxes = await client.list({
|
|
728
|
+
...input2.teamId ? { teamId: input2.teamId } : {},
|
|
729
|
+
...input2.projectId ? { projectId: input2.projectId } : {},
|
|
730
|
+
...input2.agentId ? { agentId: input2.agentId } : {}
|
|
731
|
+
});
|
|
732
|
+
const match = sandboxes.filter((sandbox) => {
|
|
733
|
+
if (!sandbox.repo) return false;
|
|
734
|
+
if (normalizeSandboxTemplateRepoIdentity(sandbox.repo) !== repoIdentity)
|
|
735
|
+
return false;
|
|
736
|
+
const createdAt = Date.parse(sandbox.createdAt);
|
|
737
|
+
if (Number.isFinite(createdAt) && createdAt < requestedAt - 3e4)
|
|
738
|
+
return false;
|
|
739
|
+
if (metadata.source && sandbox.metadata?.source !== metadata.source)
|
|
740
|
+
return false;
|
|
741
|
+
return true;
|
|
742
|
+
}).sort(
|
|
743
|
+
(left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt)
|
|
744
|
+
)[0];
|
|
745
|
+
if (match?.state === "running" || match?.state === "stopped") {
|
|
746
|
+
return match;
|
|
747
|
+
}
|
|
748
|
+
if (match?.state === "error") {
|
|
749
|
+
throw new Error(
|
|
750
|
+
`sandbox create failed after timeout: ${match.id}
|
|
751
|
+
${match.logs.join(
|
|
752
|
+
"\n"
|
|
753
|
+
)}`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
await sleep(pollMs);
|
|
757
|
+
}
|
|
758
|
+
throw new Error(
|
|
759
|
+
"sandbox create timed out and no matching created sandbox reached running state"
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
async function waitForSandboxTemplateRunnerReady(client, sandboxId) {
|
|
763
|
+
const timeoutMs = 12e4;
|
|
764
|
+
const pollMs = 3e3;
|
|
765
|
+
const deadline = Date.now() + timeoutMs;
|
|
766
|
+
let lastError = null;
|
|
767
|
+
while (Date.now() < deadline) {
|
|
768
|
+
try {
|
|
769
|
+
const result = await client.exec(sandboxId, {
|
|
770
|
+
command: "true",
|
|
771
|
+
timeoutSeconds: 30
|
|
772
|
+
});
|
|
773
|
+
if (result.command.status === "succeeded" && result.command.exitCode === 0) {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
lastError = new Error(
|
|
777
|
+
`readiness command ${result.command.status} with exit code ${String(
|
|
778
|
+
result.command.exitCode
|
|
779
|
+
)}`
|
|
780
|
+
);
|
|
781
|
+
} catch (error) {
|
|
782
|
+
lastError = error;
|
|
783
|
+
if (!isRetryableSandboxRunnerReadyError(error)) {
|
|
784
|
+
throw error;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
await sleep(pollMs);
|
|
788
|
+
}
|
|
789
|
+
throw new Error(
|
|
790
|
+
`sandbox runner was not ready after create: ${formatUnknownError(
|
|
791
|
+
lastError
|
|
792
|
+
)}`
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
function isRetryableSandboxRunnerReadyError(error) {
|
|
796
|
+
return error instanceof Error && /\b(502|503|504|timed out|timeout|sandbox_not_found|sandbox_not_ready|sandbox_runner_failed)\b/i.test(
|
|
797
|
+
error.message
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
function formatUnknownError(error) {
|
|
801
|
+
if (error instanceof Error) return error.message;
|
|
802
|
+
if (typeof error === "string") return error;
|
|
803
|
+
return "unknown error";
|
|
804
|
+
}
|
|
805
|
+
function sleep(ms) {
|
|
806
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
807
|
+
}
|
|
808
|
+
async function resolveSandboxTemplateStartInput(manifest, options, projectPath) {
|
|
809
|
+
const scalars = parseSandboxTemplateScalarInputs(options);
|
|
810
|
+
const uploadSpecs = collectSandboxTemplateUploadSpecs(manifest);
|
|
811
|
+
const uploadRequests = [];
|
|
812
|
+
const fileInputs = parseSandboxTemplateFileInputOptions(options);
|
|
813
|
+
for (const [inputName, rawValue] of Object.entries(fileInputs)) {
|
|
814
|
+
const spec = uploadSpecs.get(inputName);
|
|
815
|
+
if (!spec) {
|
|
816
|
+
throw new Error(
|
|
817
|
+
`${inputName} is not declared as a file upload input in ${OPENPOND_MANIFEST_FILE_NAME}`
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
const localPaths = await expandSandboxTemplateUploadPaths(
|
|
821
|
+
rawValue,
|
|
822
|
+
projectPath
|
|
823
|
+
);
|
|
824
|
+
if (!spec.multiple && localPaths.length > 1) {
|
|
825
|
+
throw new Error(
|
|
826
|
+
`${inputName} accepts one file, got ${localPaths.length}`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
uploadRequests.push({ inputName, localPaths, spec });
|
|
830
|
+
}
|
|
831
|
+
const requiredInputs = Array.isArray(manifest.inputs.schema.required) ? manifest.inputs.schema.required.filter(
|
|
832
|
+
(value) => typeof value === "string"
|
|
833
|
+
) : [];
|
|
834
|
+
for (const inputName of requiredInputs) {
|
|
835
|
+
if (uploadSpecs.has(inputName) && !uploadRequests.some((request) => request.inputName === inputName)) {
|
|
836
|
+
throw new Error(
|
|
837
|
+
`${inputName} is required; pass --input-file ${inputName}=<path> or --input-files ${inputName}=<glob>`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return { scalars, uploadRequests };
|
|
842
|
+
}
|
|
843
|
+
function parseSandboxTemplateScalarInputs(options) {
|
|
844
|
+
const rawInputs = typeof options.inputs === "string" ? options.inputs : typeof options.inputJson === "string" ? options.inputJson : typeof options.params === "string" ? options.params : "";
|
|
845
|
+
const scalars = rawInputs ? parseJsonOption(rawInputs, "inputs") : {};
|
|
846
|
+
if (!scalars || typeof scalars !== "object" || Array.isArray(scalars)) {
|
|
847
|
+
throw new Error("inputs must be a JSON object");
|
|
848
|
+
}
|
|
849
|
+
const rawInput = typeof options.input === "string" ? options.input.trim() : "";
|
|
850
|
+
if (rawInput) {
|
|
851
|
+
const [name, value] = parseKeyValueOption(rawInput, "input");
|
|
852
|
+
scalars[name] = value;
|
|
853
|
+
}
|
|
854
|
+
return scalars;
|
|
855
|
+
}
|
|
856
|
+
function parseSandboxTemplateFileInputOptions(options) {
|
|
857
|
+
const out = {};
|
|
858
|
+
const rawFile = typeof options.inputFile === "string" ? options.inputFile.trim() : "";
|
|
859
|
+
if (rawFile) {
|
|
860
|
+
const [name, value] = parseKeyValueOption(rawFile, "input-file");
|
|
861
|
+
out[name] = value;
|
|
862
|
+
}
|
|
863
|
+
const rawFiles = typeof options.inputFiles === "string" ? options.inputFiles.trim() : "";
|
|
864
|
+
if (rawFiles) {
|
|
865
|
+
const [name, value] = parseKeyValueOption(rawFiles, "input-files");
|
|
866
|
+
out[name] = value;
|
|
867
|
+
}
|
|
868
|
+
return out;
|
|
869
|
+
}
|
|
870
|
+
function parseKeyValueOption(value, label) {
|
|
871
|
+
const index = value.indexOf("=");
|
|
872
|
+
if (index <= 0) {
|
|
873
|
+
throw new Error(`${label} must be formatted as name=value`);
|
|
874
|
+
}
|
|
875
|
+
const key = value.slice(0, index).trim();
|
|
876
|
+
const raw = value.slice(index + 1).trim();
|
|
877
|
+
if (!key || !raw) {
|
|
878
|
+
throw new Error(`${label} must be formatted as name=value`);
|
|
879
|
+
}
|
|
880
|
+
return [key, raw];
|
|
881
|
+
}
|
|
882
|
+
function collectSandboxTemplateUploadSpecs(manifest) {
|
|
883
|
+
const properties = asPlainRecord(manifest.inputs.schema.properties) ?? {};
|
|
884
|
+
const specs = /* @__PURE__ */ new Map();
|
|
885
|
+
for (const [inputName, rawProperty] of Object.entries(properties)) {
|
|
886
|
+
const property = asPlainRecord(rawProperty);
|
|
887
|
+
if (!property) continue;
|
|
888
|
+
const upload = asPlainRecord(
|
|
889
|
+
property["x-openpond-upload"] ?? property.xOpenPondUpload
|
|
890
|
+
);
|
|
891
|
+
if (!upload) continue;
|
|
892
|
+
const targetPath = typeof upload.targetPath === "string" && upload.targetPath.trim() ? normalizeSandboxUploadTargetPath(upload.targetPath) : "";
|
|
893
|
+
if (!targetPath) {
|
|
894
|
+
throw new Error(`${inputName} upload metadata is missing targetPath`);
|
|
895
|
+
}
|
|
896
|
+
const multiple = upload.multiple === true || property.type === "array" || asPlainRecord(property.items)?.format === "file";
|
|
897
|
+
specs.set(inputName, { inputName, multiple, targetPath });
|
|
898
|
+
}
|
|
899
|
+
return specs;
|
|
900
|
+
}
|
|
901
|
+
function asPlainRecord(value) {
|
|
902
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
903
|
+
return value;
|
|
904
|
+
}
|
|
905
|
+
function normalizeSandboxUploadTargetPath(value) {
|
|
906
|
+
const normalized = value.trim().replace(/\\/g, "/").replace(/^\/workspace\//, "").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
907
|
+
if (!normalized || normalized.split("/").some((segment) => segment === ".." || segment === "")) {
|
|
908
|
+
throw new Error(`invalid upload target path: ${value}`);
|
|
909
|
+
}
|
|
910
|
+
if (normalized.split("/").some(isSandboxEnvFileName)) {
|
|
911
|
+
throw new Error(
|
|
912
|
+
"sandbox template uploads cannot target .env* files; create sandbox secrets and pass refs with --env-ref"
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
return normalized;
|
|
916
|
+
}
|
|
917
|
+
async function expandSandboxTemplateUploadPaths(rawValue, projectPath) {
|
|
918
|
+
const values = rawValue.split(",").map((item) => item.trim()).filter(Boolean);
|
|
919
|
+
const paths = (await Promise.all(
|
|
920
|
+
values.map((value) => expandSandboxTemplateUploadPath(value, projectPath))
|
|
921
|
+
)).flat();
|
|
922
|
+
if (paths.length === 0) {
|
|
923
|
+
throw new Error(`no files matched ${rawValue}`);
|
|
924
|
+
}
|
|
925
|
+
return paths;
|
|
926
|
+
}
|
|
927
|
+
async function expandSandboxTemplateUploadPath(rawValue, projectPath) {
|
|
928
|
+
const absolute = path2.resolve(projectPath, rawValue);
|
|
929
|
+
if (!rawValue.includes("*")) {
|
|
930
|
+
assertSandboxTemplateUploadPathAllowed(absolute);
|
|
931
|
+
const stats = await fs.stat(absolute);
|
|
932
|
+
if (!stats.isFile()) {
|
|
933
|
+
throw new Error(`upload path is not a file: ${absolute}`);
|
|
934
|
+
}
|
|
935
|
+
return [absolute];
|
|
936
|
+
}
|
|
937
|
+
const directory = path2.dirname(absolute);
|
|
938
|
+
const basename = path2.basename(absolute);
|
|
939
|
+
const regex = globBasenameToRegExp(basename);
|
|
940
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
941
|
+
const matchingEntries = entries.filter((entry) => entry.isFile() && regex.test(entry.name)).sort((left, right) => left.name.localeCompare(right.name));
|
|
942
|
+
const blocked = matchingEntries.find(
|
|
943
|
+
(entry) => isSandboxEnvFileName(entry.name)
|
|
944
|
+
);
|
|
945
|
+
if (blocked) {
|
|
946
|
+
throw new Error(
|
|
947
|
+
"sandbox template uploads cannot include .env* files; create sandbox secrets and pass refs with --env-ref"
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
return matchingEntries.map((entry) => path2.join(directory, entry.name)).sort((left, right) => left.localeCompare(right));
|
|
951
|
+
}
|
|
952
|
+
function assertSandboxTemplateUploadPathAllowed(localPath) {
|
|
953
|
+
if (isSandboxEnvFileName(path2.basename(localPath))) {
|
|
954
|
+
throw new Error(
|
|
955
|
+
"sandbox template uploads cannot include .env* files; create sandbox secrets and pass refs with --env-ref"
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function isSandboxEnvFileName(name) {
|
|
960
|
+
return name === ".env" || name.startsWith(".env.");
|
|
961
|
+
}
|
|
962
|
+
function globBasenameToRegExp(pattern) {
|
|
963
|
+
const escaped = pattern.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
964
|
+
return new RegExp(`^${escaped}$`);
|
|
965
|
+
}
|
|
966
|
+
async function runSandboxTemplateSetupCommands(client, sandboxId, manifest, options) {
|
|
967
|
+
const timeoutSeconds = parseIntegerOption(options.setupTimeoutSeconds, "setup-timeout-seconds") ?? 900;
|
|
968
|
+
const results = [];
|
|
969
|
+
for (const command of manifest.setup.commands) {
|
|
970
|
+
const result = await runSandboxTemplateShellCommand(
|
|
971
|
+
client,
|
|
972
|
+
sandboxId,
|
|
973
|
+
command,
|
|
974
|
+
timeoutSeconds
|
|
975
|
+
);
|
|
976
|
+
results.push({
|
|
977
|
+
command,
|
|
978
|
+
status: result.status,
|
|
979
|
+
exitCode: result.exitCode
|
|
980
|
+
});
|
|
981
|
+
if (result.status !== "succeeded" || result.exitCode !== 0) {
|
|
982
|
+
throw new Error(`setup command failed: ${command}
|
|
983
|
+
${result.output}`);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return results;
|
|
987
|
+
}
|
|
988
|
+
async function uploadSandboxTemplateStartFiles(client, sandboxId, requests) {
|
|
989
|
+
const uploaded = [];
|
|
990
|
+
for (const request of requests) {
|
|
991
|
+
for (const localPath of request.localPaths) {
|
|
992
|
+
const contents = await fs.readFile(localPath);
|
|
993
|
+
const sandboxPath = joinSandboxUploadPath(
|
|
994
|
+
request.spec.targetPath,
|
|
995
|
+
path2.basename(localPath)
|
|
996
|
+
);
|
|
997
|
+
await client.uploadFileBase64(
|
|
998
|
+
sandboxId,
|
|
999
|
+
sandboxPath,
|
|
1000
|
+
contents.toString("base64")
|
|
1001
|
+
);
|
|
1002
|
+
uploaded.push({
|
|
1003
|
+
inputName: request.inputName,
|
|
1004
|
+
localPath,
|
|
1005
|
+
sandboxPath,
|
|
1006
|
+
sizeBytes: contents.byteLength
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return uploaded;
|
|
1011
|
+
}
|
|
1012
|
+
function joinSandboxUploadPath(targetPath, basename) {
|
|
1013
|
+
return `${targetPath.replace(/\/+$/, "")}/${basename.replace(/^\/+/, "")}`;
|
|
1014
|
+
}
|
|
1015
|
+
function formatUploadedFileParams(uploadedFiles, requests) {
|
|
1016
|
+
const out = {};
|
|
1017
|
+
for (const request of requests) {
|
|
1018
|
+
const files = uploadedFiles.filter((file) => file.inputName === request.inputName).map((file) => file.sandboxPath);
|
|
1019
|
+
out[request.inputName] = request.spec.multiple ? files : files[0] ?? "";
|
|
1020
|
+
}
|
|
1021
|
+
return out;
|
|
1022
|
+
}
|
|
1023
|
+
async function runSandboxTemplateExecutable(client, sandboxId, executable, input2, env = {}) {
|
|
1024
|
+
await uploadSandboxTemplateReplayParams(client, sandboxId, input2);
|
|
1025
|
+
const command = formatSandboxTemplateCommand(executable, env);
|
|
1026
|
+
const timeoutSeconds = executable.timeoutSeconds;
|
|
1027
|
+
if (executable.kind === "service") {
|
|
1028
|
+
const result2 = await client.startProcess(sandboxId, {
|
|
1029
|
+
command,
|
|
1030
|
+
...timeoutSeconds !== void 0 ? { timeoutSeconds } : {}
|
|
1031
|
+
});
|
|
1032
|
+
return { kind: "service", process: result2.process };
|
|
1033
|
+
}
|
|
1034
|
+
const result = await runSandboxTemplateProcessToCompletion(
|
|
1035
|
+
client,
|
|
1036
|
+
sandboxId,
|
|
1037
|
+
command,
|
|
1038
|
+
timeoutSeconds ?? 900
|
|
1039
|
+
);
|
|
1040
|
+
if (result.status !== "succeeded" || result.exitCode !== 0) {
|
|
1041
|
+
throw new Error(
|
|
1042
|
+
`template command failed: ${executable.name}
|
|
1043
|
+
${result.output}`
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
return { kind: executable.kind, process: result };
|
|
1047
|
+
}
|
|
1048
|
+
async function runSandboxTemplateShellCommand(client, sandboxId, command, timeoutSeconds) {
|
|
1049
|
+
return runSandboxTemplateProcessToCompletion(
|
|
1050
|
+
client,
|
|
1051
|
+
sandboxId,
|
|
1052
|
+
command,
|
|
1053
|
+
timeoutSeconds ?? 900
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
async function uploadSandboxTemplateReplayParams(client, sandboxId, input2) {
|
|
1057
|
+
const paramsJson = `${JSON.stringify({ input: input2 }, null, 2)}
|
|
1058
|
+
`;
|
|
1059
|
+
await client.uploadFileBase64(
|
|
1060
|
+
sandboxId,
|
|
1061
|
+
"openpond-replay-params.json",
|
|
1062
|
+
Buffer.from(paramsJson, "utf8").toString("base64")
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
async function runSandboxTemplateProcessToCompletion(client, sandboxId, command, timeoutSeconds) {
|
|
1066
|
+
const started = await client.startProcess(sandboxId, {
|
|
1067
|
+
command,
|
|
1068
|
+
timeoutSeconds
|
|
1069
|
+
});
|
|
1070
|
+
let current = started.process;
|
|
1071
|
+
const deadline = Date.now() + timeoutSeconds * 1e3 + 3e4;
|
|
1072
|
+
while (current.status === "running" && Date.now() < deadline) {
|
|
1073
|
+
await sleep(3e3);
|
|
1074
|
+
const polled = await client.getProcess(sandboxId, current.id);
|
|
1075
|
+
current = polled.process;
|
|
1076
|
+
}
|
|
1077
|
+
return {
|
|
1078
|
+
command: current.command,
|
|
1079
|
+
status: current.status,
|
|
1080
|
+
output: current.output,
|
|
1081
|
+
exitCode: current.exitCode,
|
|
1082
|
+
processId: current.id
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
function sandboxTemplateRuntimeEnv(sandbox) {
|
|
1086
|
+
return {
|
|
1087
|
+
OPENPOND_SANDBOX_ID: sandbox.id,
|
|
1088
|
+
...sandbox.runtimeId ? { OPENPOND_SANDBOX_RUNTIME_ID: sandbox.runtimeId } : {}
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function formatSandboxTemplateCommand(executable, env = {}) {
|
|
1092
|
+
const paramsPath = quoteShellArg(replayParamsPathForExecutable(executable));
|
|
1093
|
+
const envPrefix = `OPENPOND_REPLAY_PARAMS_BASE64="$(base64 -w0 ${paramsPath} 2>/dev/null || base64 ${paramsPath} | tr -d '\\n')"`;
|
|
1094
|
+
const runtimeEnvPrefix = Object.entries(env).filter(([, value]) => value.length > 0).map(([name, value]) => `${name}=${quoteShellArg(value)}`).join(" ");
|
|
1095
|
+
const command = [runtimeEnvPrefix, envPrefix, executable.command].filter(Boolean).join(" ");
|
|
1096
|
+
if (!executable.cwd) return command;
|
|
1097
|
+
return `cd ${quoteShellArg(executable.cwd)} && ${command}`;
|
|
1098
|
+
}
|
|
1099
|
+
function replayParamsPathForExecutable(executable) {
|
|
1100
|
+
if (!executable.cwd) return "openpond-replay-params.json";
|
|
1101
|
+
const cwd = executable.cwd.replace(/\\/g, "/").replace(/\/+$/, "") || ".";
|
|
1102
|
+
return path2.posix.relative(cwd, "openpond-replay-params.json") || "openpond-replay-params.json";
|
|
1103
|
+
}
|
|
1104
|
+
function quoteShellArg(value) {
|
|
1105
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
1106
|
+
}
|
|
1107
|
+
async function openSandboxTemplatePorts(client, sandboxId, ports) {
|
|
1108
|
+
const previews = [];
|
|
1109
|
+
for (const port of ports) {
|
|
1110
|
+
const result = await client.openPort(sandboxId, {
|
|
1111
|
+
port: port.port,
|
|
1112
|
+
...port.label ? { label: port.label } : {},
|
|
1113
|
+
access: port.access,
|
|
1114
|
+
autoStart: false
|
|
1115
|
+
});
|
|
1116
|
+
previews.push(result.preview);
|
|
1117
|
+
}
|
|
1118
|
+
return previews;
|
|
1119
|
+
}
|
|
1120
|
+
function resolveSandboxTemplateFilePath(options) {
|
|
1121
|
+
const rawFile = typeof options.file === "string" && options.file.trim().length > 0 ? options.file.trim() : typeof options.manifest === "string" && options.manifest.trim().length > 0 ? options.manifest.trim() : OPENPOND_MANIFEST_FILE_NAME;
|
|
1122
|
+
return path2.resolve(process.cwd(), rawFile);
|
|
1123
|
+
}
|
|
1124
|
+
function resolveSandboxTemplateScaffoldPath(options) {
|
|
1125
|
+
const rawPath = typeof options.path === "string" && options.path.trim().length > 0 ? options.path.trim() : typeof options.dir === "string" && options.dir.trim().length > 0 ? options.dir.trim() : process.cwd();
|
|
1126
|
+
return path2.resolve(process.cwd(), rawPath);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// src/cli/sandbox-template-local.ts
|
|
1130
|
+
async function runSandboxTemplateLocal(options, mode) {
|
|
1131
|
+
const { plan, filePath, projectPath } = await loadSandboxTemplateBuildPlan(
|
|
1132
|
+
options
|
|
1133
|
+
);
|
|
1134
|
+
const executable = resolveSandboxTemplateLocalExecutable(
|
|
1135
|
+
plan.manifest,
|
|
1136
|
+
options,
|
|
1137
|
+
mode
|
|
1138
|
+
);
|
|
1139
|
+
const input2 = await resolveSandboxTemplateStartInput(
|
|
1140
|
+
plan.manifest,
|
|
1141
|
+
options,
|
|
1142
|
+
projectPath
|
|
1143
|
+
);
|
|
1144
|
+
const volumes = await prepareLocalSandboxTemplateVolumes(
|
|
1145
|
+
plan.manifest,
|
|
1146
|
+
projectPath
|
|
1147
|
+
);
|
|
1148
|
+
const setupCommands = await runLocalSandboxTemplateSetupCommands(
|
|
1149
|
+
plan.manifest,
|
|
1150
|
+
projectPath,
|
|
1151
|
+
options
|
|
1152
|
+
);
|
|
1153
|
+
const uploadedFiles = await prepareLocalSandboxTemplateUploads(
|
|
1154
|
+
input2.uploadRequests,
|
|
1155
|
+
projectPath
|
|
1156
|
+
);
|
|
1157
|
+
const commandInput = {
|
|
1158
|
+
...input2.scalars,
|
|
1159
|
+
...formatUploadedFileParams(uploadedFiles, input2.uploadRequests)
|
|
1160
|
+
};
|
|
1161
|
+
const replay = await writeLocalSandboxTemplateReplayParams(
|
|
1162
|
+
projectPath,
|
|
1163
|
+
executable,
|
|
1164
|
+
commandInput
|
|
1165
|
+
);
|
|
1166
|
+
const previews = localSandboxTemplatePreviews(executable);
|
|
1167
|
+
if (mode === "dev" || executable.kind === "service") {
|
|
1168
|
+
console.log(
|
|
1169
|
+
JSON.stringify(
|
|
1170
|
+
{
|
|
1171
|
+
ok: true,
|
|
1172
|
+
mode,
|
|
1173
|
+
file: filePath,
|
|
1174
|
+
template: plan.manifest.name,
|
|
1175
|
+
executable: summarizeSandboxTemplateExecutable(executable),
|
|
1176
|
+
volumes,
|
|
1177
|
+
setupCommands,
|
|
1178
|
+
uploadedFiles,
|
|
1179
|
+
input: commandInput,
|
|
1180
|
+
replayParamsPath: replay.paramsPath,
|
|
1181
|
+
previews,
|
|
1182
|
+
status: "starting"
|
|
1183
|
+
},
|
|
1184
|
+
null,
|
|
1185
|
+
2
|
|
1186
|
+
)
|
|
1187
|
+
);
|
|
1188
|
+
const result = await runLocalSandboxTemplateCommand(
|
|
1189
|
+
projectPath,
|
|
1190
|
+
executable,
|
|
1191
|
+
replay,
|
|
1192
|
+
{
|
|
1193
|
+
inherit: true,
|
|
1194
|
+
timeoutSeconds: executable.timeoutSeconds
|
|
1195
|
+
}
|
|
1196
|
+
);
|
|
1197
|
+
if (result.status !== "succeeded") process.exitCode = 1;
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
const execution = await runLocalSandboxTemplateCommand(
|
|
1201
|
+
projectPath,
|
|
1202
|
+
executable,
|
|
1203
|
+
replay,
|
|
1204
|
+
{
|
|
1205
|
+
timeoutSeconds: executable.timeoutSeconds ?? 900
|
|
1206
|
+
}
|
|
1207
|
+
);
|
|
1208
|
+
const artifacts = await collectLocalSandboxTemplateArtifacts(
|
|
1209
|
+
projectPath,
|
|
1210
|
+
executable.artifactPaths
|
|
1211
|
+
);
|
|
1212
|
+
if (execution.status !== "succeeded") process.exitCode = 1;
|
|
1213
|
+
console.log(
|
|
1214
|
+
JSON.stringify(
|
|
1215
|
+
{
|
|
1216
|
+
ok: execution.status === "succeeded",
|
|
1217
|
+
mode,
|
|
1218
|
+
file: filePath,
|
|
1219
|
+
template: plan.manifest.name,
|
|
1220
|
+
executable: summarizeSandboxTemplateExecutable(executable),
|
|
1221
|
+
volumes,
|
|
1222
|
+
setupCommands,
|
|
1223
|
+
uploadedFiles,
|
|
1224
|
+
input: commandInput,
|
|
1225
|
+
replayParamsPath: replay.paramsPath,
|
|
1226
|
+
execution,
|
|
1227
|
+
previews,
|
|
1228
|
+
artifacts
|
|
1229
|
+
},
|
|
1230
|
+
null,
|
|
1231
|
+
2
|
|
1232
|
+
)
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
async function runSandboxTemplateExistingSandboxAction(options, rest) {
|
|
1236
|
+
const sandboxId = rest[0]?.trim() || (typeof options.sandboxId === "string" ? options.sandboxId.trim() : "");
|
|
1237
|
+
const actionName = rest[1]?.trim() || (typeof options.action === "string" ? options.action.trim() : "") || (typeof options.target === "string" ? options.target.trim() : "");
|
|
1238
|
+
if (!sandboxId || !actionName) {
|
|
1239
|
+
throw new Error(
|
|
1240
|
+
`usage: sandbox-template action <sandboxId> <actionName> [--file ${OPENPOND_MANIFEST_FILE_NAME}]`
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
const { plan, filePath, projectPath } = await loadSandboxTemplateBuildPlan(
|
|
1244
|
+
options
|
|
1245
|
+
);
|
|
1246
|
+
const executable = resolveSandboxTemplateActionExecutable(
|
|
1247
|
+
plan.manifest,
|
|
1248
|
+
actionName
|
|
1249
|
+
);
|
|
1250
|
+
const input2 = await resolveSandboxTemplateStartInput(
|
|
1251
|
+
plan.manifest,
|
|
1252
|
+
options,
|
|
1253
|
+
projectPath
|
|
1254
|
+
);
|
|
1255
|
+
const client = await resolveSandboxClient(options);
|
|
1256
|
+
const uploadedFiles = await uploadSandboxTemplateStartFiles(
|
|
1257
|
+
client,
|
|
1258
|
+
sandboxId,
|
|
1259
|
+
input2.uploadRequests
|
|
1260
|
+
);
|
|
1261
|
+
const commandInput = {
|
|
1262
|
+
...input2.scalars,
|
|
1263
|
+
...formatUploadedFileParams(uploadedFiles, input2.uploadRequests)
|
|
1264
|
+
};
|
|
1265
|
+
const execution = await runSandboxTemplateExecutable(
|
|
1266
|
+
client,
|
|
1267
|
+
sandboxId,
|
|
1268
|
+
executable,
|
|
1269
|
+
commandInput
|
|
1270
|
+
);
|
|
1271
|
+
console.log(
|
|
1272
|
+
JSON.stringify(
|
|
1273
|
+
{
|
|
1274
|
+
ok: true,
|
|
1275
|
+
file: filePath,
|
|
1276
|
+
sandboxId,
|
|
1277
|
+
executable: summarizeSandboxTemplateExecutable(executable),
|
|
1278
|
+
uploadedFiles,
|
|
1279
|
+
input: commandInput,
|
|
1280
|
+
execution,
|
|
1281
|
+
expectedArtifacts: executable.artifactPaths
|
|
1282
|
+
},
|
|
1283
|
+
null,
|
|
1284
|
+
2
|
|
1285
|
+
)
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
function resolveSandboxTemplateLocalExecutable(manifest, options, mode) {
|
|
1289
|
+
if (mode === "dev" && typeof options.target !== "string" && typeof options.action !== "string" && typeof options.service !== "string" && typeof options.entrypoint !== "string") {
|
|
1290
|
+
const firstService = sandboxTemplateExecutableEntries(manifest).find(
|
|
1291
|
+
(candidate) => candidate.kind === "service"
|
|
1292
|
+
);
|
|
1293
|
+
if (firstService) return firstService;
|
|
1294
|
+
}
|
|
1295
|
+
return resolveSandboxTemplateStartExecutable(manifest, options);
|
|
1296
|
+
}
|
|
1297
|
+
function resolveSandboxTemplateActionExecutable(manifest, actionName) {
|
|
1298
|
+
const match = sandboxTemplateExecutableEntries(manifest).find(
|
|
1299
|
+
(candidate) => candidate.kind === "action" && candidate.name === actionName
|
|
1300
|
+
);
|
|
1301
|
+
if (!match) {
|
|
1302
|
+
const actions = manifest.actions.map((action) => action.name).join(", ") || "(none)";
|
|
1303
|
+
throw new Error(
|
|
1304
|
+
`manifest action not found: ${actionName}. Available actions: ${actions}`
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
return match;
|
|
1308
|
+
}
|
|
1309
|
+
function summarizeSandboxTemplateExecutable(executable) {
|
|
1310
|
+
return {
|
|
1311
|
+
kind: executable.kind,
|
|
1312
|
+
name: executable.name,
|
|
1313
|
+
command: executable.command,
|
|
1314
|
+
cwd: executable.cwd ?? null,
|
|
1315
|
+
timeoutSeconds: executable.timeoutSeconds ?? null,
|
|
1316
|
+
ports: executable.ports,
|
|
1317
|
+
artifactPaths: executable.artifactPaths
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
async function prepareLocalSandboxTemplateVolumes(manifest, projectPath) {
|
|
1321
|
+
const volumes = [];
|
|
1322
|
+
for (const volume of manifest.volumes) {
|
|
1323
|
+
const mountPath = typeof volume.mountPath === "string" && volume.mountPath.trim() ? volume.mountPath.trim() : volume.name ? `/workspace/volumes/${volume.name}` : "";
|
|
1324
|
+
if (!mountPath) continue;
|
|
1325
|
+
const localPath = path3.resolve(
|
|
1326
|
+
projectPath,
|
|
1327
|
+
normalizeLocalWorkspacePath(mountPath)
|
|
1328
|
+
);
|
|
1329
|
+
await fs2.mkdir(localPath, { recursive: true });
|
|
1330
|
+
volumes.push({
|
|
1331
|
+
name: volume.name ?? null,
|
|
1332
|
+
mountPath,
|
|
1333
|
+
localPath
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
return volumes;
|
|
1337
|
+
}
|
|
1338
|
+
async function runLocalSandboxTemplateSetupCommands(manifest, projectPath, options) {
|
|
1339
|
+
const timeoutSeconds = parseIntegerOption(options.setupTimeoutSeconds, "setup-timeout-seconds") ?? 900;
|
|
1340
|
+
const results = [];
|
|
1341
|
+
for (const command of manifest.setup.commands) {
|
|
1342
|
+
const result = await runLocalShellCommandResult(command, projectPath, {
|
|
1343
|
+
timeoutSeconds
|
|
1344
|
+
});
|
|
1345
|
+
results.push(result);
|
|
1346
|
+
if (result.status !== "succeeded") {
|
|
1347
|
+
throw new Error(
|
|
1348
|
+
`local setup command failed: ${command}
|
|
1349
|
+
${result.output}`
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return results;
|
|
1354
|
+
}
|
|
1355
|
+
async function prepareLocalSandboxTemplateUploads(requests, projectPath) {
|
|
1356
|
+
const uploaded = [];
|
|
1357
|
+
for (const request of requests) {
|
|
1358
|
+
for (const localPath of request.localPaths) {
|
|
1359
|
+
const contents = await fs2.readFile(localPath);
|
|
1360
|
+
const sandboxPath = joinSandboxUploadPath(
|
|
1361
|
+
request.spec.targetPath,
|
|
1362
|
+
path3.basename(localPath)
|
|
1363
|
+
);
|
|
1364
|
+
const destination = path3.resolve(
|
|
1365
|
+
projectPath,
|
|
1366
|
+
normalizeLocalWorkspacePath(sandboxPath)
|
|
1367
|
+
);
|
|
1368
|
+
await fs2.mkdir(path3.dirname(destination), { recursive: true });
|
|
1369
|
+
await fs2.copyFile(localPath, destination);
|
|
1370
|
+
uploaded.push({
|
|
1371
|
+
inputName: request.inputName,
|
|
1372
|
+
localPath,
|
|
1373
|
+
sandboxPath,
|
|
1374
|
+
sizeBytes: contents.byteLength
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return uploaded;
|
|
1379
|
+
}
|
|
1380
|
+
async function writeLocalSandboxTemplateReplayParams(projectPath, executable, input2) {
|
|
1381
|
+
const paramsJson = `${JSON.stringify({ input: input2 }, null, 2)}
|
|
1382
|
+
`;
|
|
1383
|
+
const encoded = Buffer2.from(paramsJson, "utf8").toString("base64");
|
|
1384
|
+
const paramsPath = path3.join(projectPath, "openpond-replay-params.json");
|
|
1385
|
+
await fs2.writeFile(paramsPath, paramsJson, "utf8");
|
|
1386
|
+
if (executable.cwd) {
|
|
1387
|
+
const cwdParamsPath = path3.join(
|
|
1388
|
+
projectPath,
|
|
1389
|
+
executable.cwd,
|
|
1390
|
+
"openpond-replay-params.json"
|
|
1391
|
+
);
|
|
1392
|
+
if (cwdParamsPath !== paramsPath) {
|
|
1393
|
+
await fs2.mkdir(path3.dirname(cwdParamsPath), { recursive: true });
|
|
1394
|
+
await fs2.writeFile(cwdParamsPath, paramsJson, "utf8");
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
return { paramsPath, encoded };
|
|
1398
|
+
}
|
|
1399
|
+
async function runLocalSandboxTemplateCommand(projectPath, executable, replay, options = {}) {
|
|
1400
|
+
const cwd = executable.cwd ? path3.resolve(projectPath, executable.cwd) : projectPath;
|
|
1401
|
+
return runLocalShellCommandResult(executable.command, cwd, {
|
|
1402
|
+
env: {
|
|
1403
|
+
OPENPOND_REPLAY_PARAMS_BASE64: replay.encoded
|
|
1404
|
+
},
|
|
1405
|
+
timeoutSeconds: options.timeoutSeconds,
|
|
1406
|
+
inherit: options.inherit
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
async function runLocalShellCommandResult(command, cwd, options = {}) {
|
|
1410
|
+
const result = await runShellCommand(command, {
|
|
1411
|
+
cwd,
|
|
1412
|
+
env: options.env,
|
|
1413
|
+
timeoutSeconds: options.timeoutSeconds,
|
|
1414
|
+
inherit: options.inherit
|
|
1415
|
+
});
|
|
1416
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join("");
|
|
1417
|
+
return {
|
|
1418
|
+
command,
|
|
1419
|
+
cwd,
|
|
1420
|
+
status: result.timedOut ? "timed_out" : result.code === 0 ? "succeeded" : "failed",
|
|
1421
|
+
output,
|
|
1422
|
+
exitCode: result.code
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
function localSandboxTemplatePreviews(executable) {
|
|
1426
|
+
return executable.ports.map((port) => ({
|
|
1427
|
+
port: port.port,
|
|
1428
|
+
label: port.label ?? null,
|
|
1429
|
+
access: port.access,
|
|
1430
|
+
url: `http://127.0.0.1:${port.port}${port.path}`
|
|
1431
|
+
}));
|
|
1432
|
+
}
|
|
1433
|
+
async function collectLocalSandboxTemplateArtifacts(projectPath, artifactPaths) {
|
|
1434
|
+
const artifacts = [];
|
|
1435
|
+
for (const artifactPath of artifactPaths) {
|
|
1436
|
+
const localPath = path3.resolve(
|
|
1437
|
+
projectPath,
|
|
1438
|
+
normalizeLocalWorkspacePath(artifactPath)
|
|
1439
|
+
);
|
|
1440
|
+
try {
|
|
1441
|
+
const stat = await fs2.stat(localPath);
|
|
1442
|
+
artifacts.push({
|
|
1443
|
+
path: artifactPath,
|
|
1444
|
+
exists: stat.isFile(),
|
|
1445
|
+
sizeBytes: stat.size
|
|
1446
|
+
});
|
|
1447
|
+
} catch {
|
|
1448
|
+
artifacts.push({ path: artifactPath, exists: false, sizeBytes: null });
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
return artifacts;
|
|
1452
|
+
}
|
|
1453
|
+
function normalizeLocalWorkspacePath(value) {
|
|
1454
|
+
const normalized = value.trim().replace(/\\/g, "/").replace(/^\/workspace\//, "").replace(/^workspace\//, "").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
1455
|
+
if (!normalized || normalized.split("/").some((segment) => segment === ".." || segment === "")) {
|
|
1456
|
+
throw new Error(`invalid workspace path: ${value}`);
|
|
1457
|
+
}
|
|
1458
|
+
return normalized;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// src/cli/sandbox-template.ts
|
|
1462
|
+
async function runSandboxTemplateCommand(options, rest) {
|
|
1463
|
+
const subcommand = rest[0] || "validate";
|
|
1464
|
+
if (subcommand === "validate") {
|
|
1465
|
+
const filePath = resolveSandboxTemplateFilePath(options);
|
|
1466
|
+
const source = await fs3.readFile(filePath, "utf8");
|
|
1467
|
+
const result = validateSandboxTemplateYaml(source);
|
|
1468
|
+
if (!result.ok) {
|
|
1469
|
+
console.error(formatSandboxTemplateDiagnostics(result.diagnostics));
|
|
1470
|
+
process.exitCode = 1;
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
console.log(
|
|
1474
|
+
JSON.stringify(
|
|
1475
|
+
{
|
|
1476
|
+
ok: true,
|
|
1477
|
+
file: filePath,
|
|
1478
|
+
name: result.manifest.name,
|
|
1479
|
+
version: result.manifest.version,
|
|
1480
|
+
start: result.manifest.start.command,
|
|
1481
|
+
actions: result.manifest.actions.map((action) => action.name),
|
|
1482
|
+
services: result.manifest.services.map((service) => service.name),
|
|
1483
|
+
schedules: result.manifest.schedules.map((schedule) => schedule.name)
|
|
1484
|
+
},
|
|
1485
|
+
null,
|
|
1486
|
+
2
|
|
1487
|
+
)
|
|
1488
|
+
);
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
if (subcommand === "print-schema") {
|
|
1492
|
+
console.log(JSON.stringify(sandboxTemplateJsonSchema(), null, 2));
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
if (subcommand === "scaffold") {
|
|
1496
|
+
const outputPath = resolveSandboxTemplateScaffoldPath(options);
|
|
1497
|
+
const rawName = typeof options.name === "string" && options.name.trim().length > 0 ? options.name.trim() : path4.basename(outputPath);
|
|
1498
|
+
const description = typeof options.description === "string" && options.description.trim().length > 0 ? options.description.trim() : void 0;
|
|
1499
|
+
const files = sandboxTemplateScaffoldFiles({ name: rawName, description });
|
|
1500
|
+
const manifestPath = path4.join(outputPath, OPENPOND_MANIFEST_FILE_NAME);
|
|
1501
|
+
if (existsSync2(manifestPath)) {
|
|
1502
|
+
throw new Error(
|
|
1503
|
+
`${OPENPOND_MANIFEST_FILE_NAME} already exists at ${manifestPath}`
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
await fs3.mkdir(outputPath, { recursive: true });
|
|
1507
|
+
for (const [relativePath, contents] of Object.entries(files)) {
|
|
1508
|
+
const filePath = path4.join(outputPath, relativePath);
|
|
1509
|
+
await fs3.mkdir(path4.dirname(filePath), { recursive: true });
|
|
1510
|
+
await fs3.writeFile(filePath, contents, "utf8");
|
|
1511
|
+
}
|
|
1512
|
+
console.log(
|
|
1513
|
+
JSON.stringify(
|
|
1514
|
+
{
|
|
1515
|
+
ok: true,
|
|
1516
|
+
path: outputPath,
|
|
1517
|
+
files: Object.keys(files).sort(
|
|
1518
|
+
(left, right) => left.localeCompare(right)
|
|
1519
|
+
)
|
|
1520
|
+
},
|
|
1521
|
+
null,
|
|
1522
|
+
2
|
|
1523
|
+
)
|
|
1524
|
+
);
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
if (subcommand === "build") {
|
|
1528
|
+
await runSandboxTemplateBuild(options);
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
if (subcommand === "run") {
|
|
1532
|
+
await runSandboxTemplateLocal(options, "run");
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
if (subcommand === "dev") {
|
|
1536
|
+
await runSandboxTemplateLocal(options, "dev");
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
if (subcommand === "action") {
|
|
1540
|
+
await runSandboxTemplateExistingSandboxAction(options, rest.slice(1));
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
if (subcommand === "start") {
|
|
1544
|
+
await runSandboxTemplateStart(options);
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
throw new Error(
|
|
1548
|
+
`usage: sandbox-template <validate|print-schema|scaffold|build|run|dev|start|action> [--file ${OPENPOND_MANIFEST_FILE_NAME}] [--path <dir>] [--name <name>]`
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
async function runSandboxTemplateBuild(options) {
|
|
1552
|
+
const context = await loadSandboxTemplateManifestContext(options);
|
|
1553
|
+
const outputPath = resolveSandboxTemplateBuildOutputPath(
|
|
1554
|
+
options,
|
|
1555
|
+
context.projectPath
|
|
1556
|
+
);
|
|
1557
|
+
const plan = sandboxTemplateBuildPlan({
|
|
1558
|
+
manifest: context.manifest,
|
|
1559
|
+
manifestFile: path4.relative(context.projectPath, context.filePath) || OPENPOND_MANIFEST_FILE_NAME,
|
|
1560
|
+
projectRoot: path4.relative(path4.dirname(outputPath), context.projectPath) || "."
|
|
1561
|
+
});
|
|
1562
|
+
const shouldWrite = !parseBooleanOption(options.noWrite);
|
|
1563
|
+
if (shouldWrite) {
|
|
1564
|
+
await fs3.mkdir(path4.dirname(outputPath), { recursive: true });
|
|
1565
|
+
await fs3.writeFile(
|
|
1566
|
+
outputPath,
|
|
1567
|
+
`${JSON.stringify(plan, null, 2)}
|
|
1568
|
+
`,
|
|
1569
|
+
"utf8"
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
const metadata = sandboxTemplateBuildMetadata(context.manifest);
|
|
1573
|
+
const executables = sandboxTemplateExecutableEntries(context.manifest);
|
|
1574
|
+
console.log(
|
|
1575
|
+
JSON.stringify(
|
|
1576
|
+
{
|
|
1577
|
+
ok: true,
|
|
1578
|
+
file: context.filePath,
|
|
1579
|
+
output: shouldWrite ? outputPath : null,
|
|
1580
|
+
...metadata,
|
|
1581
|
+
startCommand: executables[0]?.command ?? null,
|
|
1582
|
+
plan
|
|
1583
|
+
},
|
|
1584
|
+
null,
|
|
1585
|
+
2
|
|
1586
|
+
)
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
async function loadSandboxTemplateManifestContext(options) {
|
|
1590
|
+
const filePath = resolveSandboxTemplateFilePath(options);
|
|
1591
|
+
const source = await fs3.readFile(filePath, "utf8");
|
|
1592
|
+
const result = validateSandboxTemplateYaml(source);
|
|
1593
|
+
if (!result.ok) {
|
|
1594
|
+
process.exitCode = 1;
|
|
1595
|
+
throw new Error(formatSandboxTemplateDiagnostics(result.diagnostics));
|
|
1596
|
+
}
|
|
1597
|
+
return {
|
|
1598
|
+
filePath,
|
|
1599
|
+
projectPath: path4.dirname(filePath),
|
|
1600
|
+
manifest: result.manifest
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
function resolveSandboxTemplateBuildOutputPath(options, projectPath) {
|
|
1604
|
+
const rawOutput = typeof options.output === "string" && options.output.trim() ? options.output.trim() : typeof options.out === "string" && options.out.trim() ? options.out.trim() : "";
|
|
1605
|
+
if (rawOutput) return path4.resolve(process.cwd(), rawOutput);
|
|
1606
|
+
const rawOutputDir = typeof options.outputDir === "string" && options.outputDir.trim() ? options.outputDir.trim() : typeof options.outDir === "string" && options.outDir.trim() ? options.outDir.trim() : "dist";
|
|
1607
|
+
return path4.resolve(
|
|
1608
|
+
projectPath,
|
|
1609
|
+
rawOutputDir,
|
|
1610
|
+
SANDBOX_TEMPLATE_BUILD_PLAN_FILE_NAME
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
async function loadSandboxTemplateBuildPlan(options) {
|
|
1614
|
+
const rawBuild = typeof options.build === "string" && options.build.trim() ? options.build.trim() : typeof options.plan === "string" && options.plan.trim() ? options.plan.trim() : "";
|
|
1615
|
+
if (rawBuild) {
|
|
1616
|
+
const filePath = path4.resolve(process.cwd(), rawBuild);
|
|
1617
|
+
const parsed = JSON.parse(
|
|
1618
|
+
await fs3.readFile(filePath, "utf8")
|
|
1619
|
+
);
|
|
1620
|
+
if (parsed.kind !== SANDBOX_TEMPLATE_BUILD_PLAN_KIND || parsed.schemaVersion !== 1) {
|
|
1621
|
+
throw new Error(`invalid sandbox template build plan: ${filePath}`);
|
|
1622
|
+
}
|
|
1623
|
+
return {
|
|
1624
|
+
plan: parsed,
|
|
1625
|
+
filePath,
|
|
1626
|
+
projectPath: path4.resolve(path4.dirname(filePath), parsed.projectRoot)
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
const context = await loadSandboxTemplateManifestContext(options);
|
|
1630
|
+
return {
|
|
1631
|
+
plan: sandboxTemplateBuildPlan({
|
|
1632
|
+
manifest: context.manifest,
|
|
1633
|
+
manifestFile: path4.relative(context.projectPath, context.filePath) || OPENPOND_MANIFEST_FILE_NAME,
|
|
1634
|
+
projectRoot: context.projectPath
|
|
1635
|
+
}),
|
|
1636
|
+
filePath: context.filePath,
|
|
1637
|
+
projectPath: context.projectPath
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
export {
|
|
1641
|
+
loadSandboxTemplateBuildPlan,
|
|
1642
|
+
loadSandboxTemplateManifestContext,
|
|
1643
|
+
resolveSandboxTemplateBuildOutputPath,
|
|
1644
|
+
runSandboxTemplateBuild,
|
|
1645
|
+
runSandboxTemplateCommand
|
|
1646
|
+
};
|