clawup 1.0.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/README.md +245 -0
- package/dist/adapters/api-adapter.d.ts +76 -0
- package/dist/adapters/api-adapter.js +250 -0
- package/dist/adapters/api-adapter.js.map +1 -0
- package/dist/adapters/cli-adapter.d.ts +15 -0
- package/dist/adapters/cli-adapter.js +208 -0
- package/dist/adapters/cli-adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +22 -0
- package/dist/adapters/index.js +32 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/types.d.ts +135 -0
- package/dist/adapters/types.js +14 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/bin.d.ts +8 -0
- package/dist/bin.js +221 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/config.d.ts +21 -0
- package/dist/commands/config.js +323 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/deploy.d.ts +7 -0
- package/dist/commands/deploy.js +13 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/destroy.d.ts +7 -0
- package/dist/commands/destroy.js +13 -0
- package/dist/commands/destroy.js.map +1 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.js +698 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +8 -0
- package/dist/commands/list.js +42 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/push.d.ts +7 -0
- package/dist/commands/push.js +19 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/redeploy.d.ts +7 -0
- package/dist/commands/redeploy.js +13 -0
- package/dist/commands/redeploy.js.map +1 -0
- package/dist/commands/secrets.d.ts +16 -0
- package/dist/commands/secrets.js +169 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +108 -0
- package/dist/commands/ssh.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +13 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.js +126 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/validate.d.ts +7 -0
- package/dist/commands/validate.js +13 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/commands/webhooks.d.ts +7 -0
- package/dist/commands/webhooks.js +13 -0
- package/dist/commands/webhooks.js.map +1 -0
- package/dist/lib/__tests__/identity.test.d.ts +1 -0
- package/dist/lib/__tests__/identity.test.js +186 -0
- package/dist/lib/__tests__/identity.test.js.map +1 -0
- package/dist/lib/__tests__/validate-agent.test.d.ts +1 -0
- package/dist/lib/__tests__/validate-agent.test.js +38 -0
- package/dist/lib/__tests__/validate-agent.test.js.map +1 -0
- package/dist/lib/config.d.ts +69 -0
- package/dist/lib/config.js +218 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/constants.d.ts +5 -0
- package/dist/lib/constants.js +29 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/exec.d.ts +24 -0
- package/dist/lib/exec.js +63 -0
- package/dist/lib/exec.js.map +1 -0
- package/dist/lib/prerequisites.d.ts +8 -0
- package/dist/lib/prerequisites.js +146 -0
- package/dist/lib/prerequisites.js.map +1 -0
- package/dist/lib/process.d.ts +18 -0
- package/dist/lib/process.js +37 -0
- package/dist/lib/process.js.map +1 -0
- package/dist/lib/pulumi.d.ts +37 -0
- package/dist/lib/pulumi.js +87 -0
- package/dist/lib/pulumi.js.map +1 -0
- package/dist/lib/tailscale.d.ts +75 -0
- package/dist/lib/tailscale.js +251 -0
- package/dist/lib/tailscale.js.map +1 -0
- package/dist/lib/tool-helpers.d.ts +15 -0
- package/dist/lib/tool-helpers.js +35 -0
- package/dist/lib/tool-helpers.js.map +1 -0
- package/dist/lib/ui.d.ts +26 -0
- package/dist/lib/ui.js +86 -0
- package/dist/lib/ui.js.map +1 -0
- package/dist/lib/update-check.d.ts +8 -0
- package/dist/lib/update-check.js +151 -0
- package/dist/lib/update-check.js.map +1 -0
- package/dist/lib/vendor.d.ts +34 -0
- package/dist/lib/vendor.js +128 -0
- package/dist/lib/vendor.js.map +1 -0
- package/dist/lib/workspace.d.ts +21 -0
- package/dist/lib/workspace.js +170 -0
- package/dist/lib/workspace.js.map +1 -0
- package/dist/tools/deploy.d.ts +16 -0
- package/dist/tools/deploy.js +181 -0
- package/dist/tools/deploy.js.map +1 -0
- package/dist/tools/destroy.d.ts +16 -0
- package/dist/tools/destroy.js +119 -0
- package/dist/tools/destroy.js.map +1 -0
- package/dist/tools/index.d.ts +20 -0
- package/dist/tools/index.js +34 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/push.d.ts +29 -0
- package/dist/tools/push.js +341 -0
- package/dist/tools/push.js.map +1 -0
- package/dist/tools/redeploy.d.ts +17 -0
- package/dist/tools/redeploy.js +181 -0
- package/dist/tools/redeploy.js.map +1 -0
- package/dist/tools/status.d.ts +16 -0
- package/dist/tools/status.js +205 -0
- package/dist/tools/status.js.map +1 -0
- package/dist/tools/validate.d.ts +16 -0
- package/dist/tools/validate.js +219 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/tools/webhooks.d.ts +17 -0
- package/dist/tools/webhooks.js +181 -0
- package/dist/tools/webhooks.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/infra/Pulumi.yaml +6 -0
- package/infra/dist/components/cloud-init.js +412 -0
- package/infra/dist/components/config-generator.js +254 -0
- package/infra/dist/components/hetzner-agent.js +162 -0
- package/infra/dist/components/index.js +18 -0
- package/infra/dist/components/openclaw-agent.js +287 -0
- package/infra/dist/components/shared.js +132 -0
- package/infra/dist/components/types.js +6 -0
- package/infra/dist/index.js +387 -0
- package/infra/dist/shared-vpc.js +167 -0
- package/infra/node_modules/@clawup/core/dist/__tests__/schemas.test.d.ts +2 -0
- package/infra/node_modules/@clawup/core/dist/__tests__/schemas.test.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/__tests__/schemas.test.js +124 -0
- package/infra/node_modules/@clawup/core/dist/__tests__/schemas.test.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/coding-agent-registry.d.ts +32 -0
- package/infra/node_modules/@clawup/core/dist/coding-agent-registry.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/coding-agent-registry.js +56 -0
- package/infra/node_modules/@clawup/core/dist/coding-agent-registry.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/constants.d.ts +137 -0
- package/infra/node_modules/@clawup/core/dist/constants.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/constants.js +314 -0
- package/infra/node_modules/@clawup/core/dist/constants.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/dep-registry.d.ts +25 -0
- package/infra/node_modules/@clawup/core/dist/dep-registry.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/dep-registry.js +46 -0
- package/infra/node_modules/@clawup/core/dist/dep-registry.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/deps.d.ts +18 -0
- package/infra/node_modules/@clawup/core/dist/deps.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/deps.js +39 -0
- package/infra/node_modules/@clawup/core/dist/deps.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/identity.d.ts +20 -0
- package/infra/node_modules/@clawup/core/dist/identity.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/identity.js +217 -0
- package/infra/node_modules/@clawup/core/dist/identity.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/index.d.ts +18 -0
- package/infra/node_modules/@clawup/core/dist/index.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/index.js +52 -0
- package/infra/node_modules/@clawup/core/dist/index.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/plugin-registry.d.ts +13 -0
- package/infra/node_modules/@clawup/core/dist/plugin-registry.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/plugin-registry.js +24 -0
- package/infra/node_modules/@clawup/core/dist/plugin-registry.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/schemas/identity.d.ts +74 -0
- package/infra/node_modules/@clawup/core/dist/schemas/identity.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/schemas/identity.js +45 -0
- package/infra/node_modules/@clawup/core/dist/schemas/identity.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/schemas/index.d.ts +6 -0
- package/infra/node_modules/@clawup/core/dist/schemas/index.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/schemas/index.js +13 -0
- package/infra/node_modules/@clawup/core/dist/schemas/index.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/schemas/manifest.d.ts +159 -0
- package/infra/node_modules/@clawup/core/dist/schemas/manifest.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/schemas/manifest.js +54 -0
- package/infra/node_modules/@clawup/core/dist/schemas/manifest.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/skills.d.ts +30 -0
- package/infra/node_modules/@clawup/core/dist/skills.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/skills.js +52 -0
- package/infra/node_modules/@clawup/core/dist/skills.js.map +1 -0
- package/infra/node_modules/@clawup/core/dist/types.d.ts +59 -0
- package/infra/node_modules/@clawup/core/dist/types.d.ts.map +1 -0
- package/infra/node_modules/@clawup/core/dist/types.js +30 -0
- package/infra/node_modules/@clawup/core/dist/types.js.map +1 -0
- package/infra/node_modules/@clawup/core/package.json +46 -0
- package/infra/package.json +12 -0
- package/package.json +43 -0
- package/scripts/postinstall.mjs +395 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Clawup - Data-Driven Multi-Agent Pulumi Stack
|
|
4
|
+
*
|
|
5
|
+
* Reads clawup.yaml manifest to dynamically deploy OpenClaw agents.
|
|
6
|
+
* The manifest is created by `clawup init` and serves as the single
|
|
7
|
+
* source of truth for the agent fleet configuration.
|
|
8
|
+
*
|
|
9
|
+
* All agents share a single VPC for cost optimization.
|
|
10
|
+
* Each agent loads workspace files from identity repos.
|
|
11
|
+
* Secrets are pulled from Pulumi config (set by CLI or ESC).
|
|
12
|
+
* Plugin configs are loaded from ~/.clawup/configs/<stack>/plugins/.
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
48
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
49
|
+
};
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
const pulumi = __importStar(require("@pulumi/pulumi"));
|
|
52
|
+
const aws = __importStar(require("@pulumi/aws"));
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
56
|
+
const components_1 = require("./components");
|
|
57
|
+
const shared_vpc_1 = require("./shared-vpc");
|
|
58
|
+
const core_1 = require("@clawup/core");
|
|
59
|
+
const identity_1 = require("@clawup/core/identity");
|
|
60
|
+
const os = __importStar(require("os"));
|
|
61
|
+
// -----------------------------------------------------------------------------
|
|
62
|
+
// Configuration from Pulumi Config / ESC
|
|
63
|
+
// -----------------------------------------------------------------------------
|
|
64
|
+
const config = new pulumi.Config();
|
|
65
|
+
const anthropicApiKey = config.requireSecret("anthropicApiKey");
|
|
66
|
+
const tailscaleAuthKey = config.requireSecret("tailscaleAuthKey");
|
|
67
|
+
const tailnetDnsName = config.require("tailnetDnsName");
|
|
68
|
+
const instanceType = config.get("instanceType") ?? "t3.medium";
|
|
69
|
+
const ownerName = config.get("ownerName") ?? "Boss";
|
|
70
|
+
const timezone = config.get("timezone") ?? "PST (America/Los_Angeles)";
|
|
71
|
+
const workingHours = config.get("workingHours") ?? "9am-6pm";
|
|
72
|
+
const userNotes = config.get("userNotes") ?? "No additional notes provided yet.";
|
|
73
|
+
// Identity cache directory
|
|
74
|
+
const identityCacheDir = path.join(os.homedir(), ".clawup", "identity-cache");
|
|
75
|
+
/**
|
|
76
|
+
* Process template placeholders in workspace files
|
|
77
|
+
*/
|
|
78
|
+
function processTemplates(files, variables) {
|
|
79
|
+
const processed = {};
|
|
80
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
81
|
+
let processedContent = content;
|
|
82
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
83
|
+
processedContent = processedContent.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
84
|
+
}
|
|
85
|
+
processed[filename] = processedContent;
|
|
86
|
+
}
|
|
87
|
+
return processed;
|
|
88
|
+
}
|
|
89
|
+
// -----------------------------------------------------------------------------
|
|
90
|
+
// Load Manifest (YAML)
|
|
91
|
+
// -----------------------------------------------------------------------------
|
|
92
|
+
// Pulumi sets cwd to the project root (where Pulumi.yaml lives)
|
|
93
|
+
const manifestPath = path.join(process.cwd(), "clawup.yaml");
|
|
94
|
+
if (!fs.existsSync(manifestPath)) {
|
|
95
|
+
throw new Error("clawup.yaml not found. Run `clawup init` to create it.");
|
|
96
|
+
}
|
|
97
|
+
// Cast as partial — old manifests may omit `provider` (defaults to "aws" below)
|
|
98
|
+
const manifest = yaml_1.default.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
99
|
+
// Load plugin configs from ~/.clawup/configs/<stackName>/plugins/
|
|
100
|
+
const pluginConfigsDir = path.join(os.homedir(), ".clawup", "configs", manifest.stackName, "plugins");
|
|
101
|
+
const pluginConfigs = {};
|
|
102
|
+
if (fs.existsSync(pluginConfigsDir)) {
|
|
103
|
+
for (const file of fs.readdirSync(pluginConfigsDir)) {
|
|
104
|
+
if (file.endsWith(".yaml")) {
|
|
105
|
+
const pluginName = file.replace(/\.yaml$/, "");
|
|
106
|
+
try {
|
|
107
|
+
const raw = fs.readFileSync(path.join(pluginConfigsDir, file), "utf-8");
|
|
108
|
+
pluginConfigs[pluginName] = yaml_1.default.parse(raw);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
pulumi.log.warn(`Failed to load plugin config '${pluginName}': ${err instanceof Error ? err.message : String(err)}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Default provider to AWS for backwards compatibility with existing manifests
|
|
117
|
+
const provider = manifest.provider ?? "aws";
|
|
118
|
+
// Validate provider
|
|
119
|
+
if (provider !== "aws" && provider !== "hetzner") {
|
|
120
|
+
throw new Error(`Unsupported provider: ${provider}. Supported providers are: aws, hetzner`);
|
|
121
|
+
}
|
|
122
|
+
// -----------------------------------------------------------------------------
|
|
123
|
+
// Resource Tags (AWS) / Labels (Hetzner)
|
|
124
|
+
// -----------------------------------------------------------------------------
|
|
125
|
+
const baseTags = {
|
|
126
|
+
Project: "clawup",
|
|
127
|
+
Environment: pulumi.getStack(),
|
|
128
|
+
ManagedBy: "pulumi",
|
|
129
|
+
};
|
|
130
|
+
// -----------------------------------------------------------------------------
|
|
131
|
+
// Provider-specific infrastructure
|
|
132
|
+
// -----------------------------------------------------------------------------
|
|
133
|
+
let sharedVpc;
|
|
134
|
+
if (provider === "aws") {
|
|
135
|
+
// -------------------------------------------------------------------------
|
|
136
|
+
// Dynamic AZ Selection - Find an AZ that supports all instance types
|
|
137
|
+
// -------------------------------------------------------------------------
|
|
138
|
+
// Collect all instance types we'll need
|
|
139
|
+
const instanceTypes = [
|
|
140
|
+
instanceType, // default from config
|
|
141
|
+
...manifest.agents.map(a => a.instanceType).filter(Boolean)
|
|
142
|
+
];
|
|
143
|
+
const uniqueInstanceTypes = [...new Set(instanceTypes)];
|
|
144
|
+
// Query AWS to find which AZs support our instance types
|
|
145
|
+
const availabilityZone = pulumi
|
|
146
|
+
.all(uniqueInstanceTypes.map((instanceType) => aws.ec2.getInstanceTypeOfferings({
|
|
147
|
+
filters: [
|
|
148
|
+
{
|
|
149
|
+
name: "instance-type",
|
|
150
|
+
values: [instanceType],
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
locationType: "availability-zone",
|
|
154
|
+
})))
|
|
155
|
+
.apply((offeringsResults) => {
|
|
156
|
+
// Build a set of AZs for each instance type
|
|
157
|
+
const azSets = offeringsResults.map((result) => new Set(result.locations));
|
|
158
|
+
// Find intersection - AZs that support ALL instance types
|
|
159
|
+
const intersection = azSets[0];
|
|
160
|
+
for (let i = 1; i < azSets.length; i++) {
|
|
161
|
+
for (const az of intersection) {
|
|
162
|
+
if (!azSets[i].has(az)) {
|
|
163
|
+
intersection.delete(az);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Pick the first available AZ alphabetically for consistency
|
|
168
|
+
const availableAzs = Array.from(intersection).sort();
|
|
169
|
+
if (availableAzs.length === 0) {
|
|
170
|
+
throw new Error(`No availability zone found that supports all instance types: ${uniqueInstanceTypes.join(", ")}`);
|
|
171
|
+
}
|
|
172
|
+
return availableAzs[0];
|
|
173
|
+
});
|
|
174
|
+
// -------------------------------------------------------------------------
|
|
175
|
+
// Shared VPC (cost optimization - all agents share one VPC)
|
|
176
|
+
// -------------------------------------------------------------------------
|
|
177
|
+
sharedVpc = new shared_vpc_1.SharedVpc("clawup", {
|
|
178
|
+
availabilityZone: availabilityZone,
|
|
179
|
+
tags: baseTags,
|
|
180
|
+
});
|
|
181
|
+
// VPC outputs
|
|
182
|
+
module.exports["vpcId"] = sharedVpc.vpcId;
|
|
183
|
+
module.exports["subnetId"] = sharedVpc.subnetId;
|
|
184
|
+
module.exports["securityGroupId"] = sharedVpc.securityGroupId;
|
|
185
|
+
module.exports["selectedAvailabilityZone"] = availabilityZone;
|
|
186
|
+
}
|
|
187
|
+
// Hetzner reads hcloud:token automatically from Pulumi config — no explicit provider needed
|
|
188
|
+
// -----------------------------------------------------------------------------
|
|
189
|
+
// Helper: Build PluginInstallConfig[] for an agent
|
|
190
|
+
// -----------------------------------------------------------------------------
|
|
191
|
+
function buildPluginsForAgent(agent, identityDefaults, identityPlugins) {
|
|
192
|
+
const plugins = [];
|
|
193
|
+
const pluginSecrets = {};
|
|
194
|
+
let enableFunnel = false;
|
|
195
|
+
const pluginList = identityPlugins ?? [];
|
|
196
|
+
for (const pluginName of pluginList) {
|
|
197
|
+
let agentSection;
|
|
198
|
+
if (agent.plugins && agent.plugins[pluginName]) {
|
|
199
|
+
// New format: inline plugin config on the agent definition
|
|
200
|
+
agentSection = agent.plugins[pluginName];
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// Backward compat: fall back to file-based plugin config
|
|
204
|
+
const pluginCfg = pluginConfigs[pluginName];
|
|
205
|
+
const userConfig = pluginCfg?.agents?.[agent.role] ?? {};
|
|
206
|
+
const identityConfig = identityDefaults?.[pluginName] ?? {};
|
|
207
|
+
agentSection = { ...identityConfig, ...userConfig };
|
|
208
|
+
}
|
|
209
|
+
const registryEntry = core_1.PLUGIN_REGISTRY[pluginName];
|
|
210
|
+
const secretMapping = registryEntry?.secretEnvVars ?? {};
|
|
211
|
+
plugins.push({
|
|
212
|
+
name: pluginName,
|
|
213
|
+
config: agentSection,
|
|
214
|
+
secretEnvVars: Object.keys(secretMapping).length > 0 ? secretMapping : undefined,
|
|
215
|
+
installable: registryEntry?.installable ?? true,
|
|
216
|
+
});
|
|
217
|
+
// Collect secret outputs from Pulumi config
|
|
218
|
+
for (const [, envVar] of Object.entries(secretMapping)) {
|
|
219
|
+
if (!pluginSecrets[envVar]) {
|
|
220
|
+
// Derive Pulumi config key from role + env var pattern
|
|
221
|
+
// e.g., LINEAR_API_KEY → <role>LinearApiKey, SLACK_BOT_TOKEN → <role>SlackBotToken
|
|
222
|
+
const secret = config.getSecret(`${agent.role}${envVarToConfigKey(envVar)}`);
|
|
223
|
+
if (secret) {
|
|
224
|
+
pluginSecrets[envVar] = secret;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Enable funnel if the plugin needs webhooks
|
|
229
|
+
if (registryEntry?.needsFunnel) {
|
|
230
|
+
enableFunnel = true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { plugins, pluginSecrets, enableFunnel };
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Convert an env var name to a Pulumi config key suffix.
|
|
237
|
+
* e.g., LINEAR_API_KEY → LinearApiKey, LINEAR_WEBHOOK_SECRET → LinearWebhookSecret
|
|
238
|
+
*/
|
|
239
|
+
function envVarToConfigKey(envVar) {
|
|
240
|
+
// Strip the common prefix (e.g., "LINEAR_") and convert to camelCase
|
|
241
|
+
// LINEAR_API_KEY → LinearApiKey
|
|
242
|
+
return envVar
|
|
243
|
+
.toLowerCase()
|
|
244
|
+
.split("_")
|
|
245
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
246
|
+
.join("");
|
|
247
|
+
}
|
|
248
|
+
// -----------------------------------------------------------------------------
|
|
249
|
+
// Dynamic Agent Deployments
|
|
250
|
+
// -----------------------------------------------------------------------------
|
|
251
|
+
const agentOutputs = {};
|
|
252
|
+
/**
|
|
253
|
+
* Build the base agent args shared by all providers.
|
|
254
|
+
* Provider-specific fields (VPC, location, tags/labels) are added by the caller.
|
|
255
|
+
*/
|
|
256
|
+
function buildBaseAgentArgs(agent) {
|
|
257
|
+
const templateVars = {
|
|
258
|
+
OWNER_NAME: ownerName,
|
|
259
|
+
TIMEZONE: timezone,
|
|
260
|
+
WORKING_HOURS: workingHours,
|
|
261
|
+
USER_NOTES: userNotes,
|
|
262
|
+
...(manifest.templateVars ?? {}),
|
|
263
|
+
};
|
|
264
|
+
// Fetch identity (always required)
|
|
265
|
+
const identity = (0, identity_1.fetchIdentitySync)(agent.identity, identityCacheDir);
|
|
266
|
+
// Identity files are the workspace files
|
|
267
|
+
const workspaceFiles = processTemplates(identity.files, templateVars);
|
|
268
|
+
// Pull defaults from identity manifest
|
|
269
|
+
const agentEmoji = identity.manifest.emoji ?? "";
|
|
270
|
+
const agentDisplayName = agent.displayName || identity.manifest.displayName;
|
|
271
|
+
const agentVolumeSize = agent.volumeSize ?? identity.manifest.volumeSize ?? 30;
|
|
272
|
+
// Extract public (clawhub) skills from identity manifest
|
|
273
|
+
const { public: publicSkills } = (0, core_1.classifySkills)(identity.manifest.skills);
|
|
274
|
+
const clawhubSkillSlugs = publicSkills.map((s) => s.slug);
|
|
275
|
+
// Build plugin configs for this agent (always from identity)
|
|
276
|
+
const { plugins, pluginSecrets, enableFunnel } = buildPluginsForAgent(agent, identity.manifest.pluginDefaults, identity.manifest.plugins);
|
|
277
|
+
// Resolve model/codingAgent from identity
|
|
278
|
+
const agentModel = identity.manifest.model ?? "anthropic/claude-opus-4-6";
|
|
279
|
+
const agentBackupModel = identity.manifest.backupModel;
|
|
280
|
+
const agentCodingAgent = identity.manifest.codingAgent ?? "claude-code";
|
|
281
|
+
// Resolve deps from identity
|
|
282
|
+
const depNames = identity.manifest.deps ?? [];
|
|
283
|
+
const resolvedDeps = (0, core_1.resolveDeps)(depNames);
|
|
284
|
+
const depEntries = resolvedDeps.map(d => ({
|
|
285
|
+
name: d.name,
|
|
286
|
+
installScript: d.entry.installScript,
|
|
287
|
+
postInstallScript: d.entry.postInstallScript,
|
|
288
|
+
secrets: Object.fromEntries(Object.entries(d.entry.secrets).map(([k, v]) => [k, { envVar: v.envVar }])),
|
|
289
|
+
}));
|
|
290
|
+
// Collect dep secrets from Pulumi config (scope-aware)
|
|
291
|
+
const depSecretDefs = (0, core_1.collectDepSecrets)(resolvedDeps);
|
|
292
|
+
const depSecrets = {};
|
|
293
|
+
for (const def of depSecretDefs) {
|
|
294
|
+
const secret = def.scope === "agent"
|
|
295
|
+
? config.getSecret(`${agent.role}${def.configKeySuffix}`)
|
|
296
|
+
: config.getSecret(`${def.configKeySuffix.charAt(0).toLowerCase()}${def.configKeySuffix.slice(1)}`);
|
|
297
|
+
if (secret) {
|
|
298
|
+
depSecrets[def.envVar] = secret;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
baseArgs: {
|
|
303
|
+
anthropicApiKey,
|
|
304
|
+
tailscaleAuthKey,
|
|
305
|
+
tailnetDnsName,
|
|
306
|
+
model: agentModel,
|
|
307
|
+
backupModel: agentBackupModel,
|
|
308
|
+
codingAgent: agentCodingAgent,
|
|
309
|
+
workspaceFiles,
|
|
310
|
+
envVars: {
|
|
311
|
+
AGENT_ROLE: agent.role,
|
|
312
|
+
AGENT_NAME: agentDisplayName,
|
|
313
|
+
AGENT_EMOJI: agentEmoji,
|
|
314
|
+
...agent.envVars,
|
|
315
|
+
},
|
|
316
|
+
plugins,
|
|
317
|
+
pluginSecrets,
|
|
318
|
+
enableFunnel,
|
|
319
|
+
clawhubSkills: clawhubSkillSlugs,
|
|
320
|
+
deps: depEntries,
|
|
321
|
+
depSecrets,
|
|
322
|
+
},
|
|
323
|
+
agentDisplayName,
|
|
324
|
+
agentVolumeSize,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
for (const agent of manifest.agents) {
|
|
328
|
+
const { baseArgs, agentVolumeSize } = buildBaseAgentArgs(agent);
|
|
329
|
+
if (provider === "aws") {
|
|
330
|
+
const agentResource = new components_1.OpenClawAgent(agent.name, {
|
|
331
|
+
...baseArgs,
|
|
332
|
+
instanceType: agent.instanceType ?? instanceType,
|
|
333
|
+
volumeSize: agentVolumeSize ?? 30,
|
|
334
|
+
vpcId: sharedVpc.vpcId,
|
|
335
|
+
subnetId: sharedVpc.subnetId,
|
|
336
|
+
securityGroupId: sharedVpc.securityGroupId,
|
|
337
|
+
tags: {
|
|
338
|
+
...baseTags,
|
|
339
|
+
AgentRole: agent.role,
|
|
340
|
+
AgentName: agent.displayName,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
agentOutputs[agent.role] = {
|
|
344
|
+
tailscaleUrl: agentResource.tailscaleUrl,
|
|
345
|
+
gatewayToken: agentResource.gatewayToken,
|
|
346
|
+
instanceId: agentResource.instanceId,
|
|
347
|
+
publicIp: agentResource.publicIp,
|
|
348
|
+
sshPrivateKey: agentResource.sshPrivateKey,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
const agentResource = new components_1.HetznerOpenClawAgent(agent.name, {
|
|
353
|
+
...baseArgs,
|
|
354
|
+
serverType: agent.instanceType ?? instanceType,
|
|
355
|
+
location: manifest.region,
|
|
356
|
+
labels: {
|
|
357
|
+
...baseTags,
|
|
358
|
+
AgentRole: agent.role,
|
|
359
|
+
AgentName: agent.displayName,
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
agentOutputs[agent.role] = {
|
|
363
|
+
tailscaleUrl: agentResource.tailscaleUrl,
|
|
364
|
+
gatewayToken: agentResource.gatewayToken,
|
|
365
|
+
instanceId: agentResource.serverId,
|
|
366
|
+
publicIp: agentResource.publicIp,
|
|
367
|
+
sshPrivateKey: agentResource.sshPrivateKey,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// -----------------------------------------------------------------------------
|
|
372
|
+
// Dynamic Stack Outputs
|
|
373
|
+
// -----------------------------------------------------------------------------
|
|
374
|
+
for (const [role, outputs] of Object.entries(agentOutputs)) {
|
|
375
|
+
module.exports[`${role}TailscaleUrl`] = pulumi.secret(outputs.tailscaleUrl);
|
|
376
|
+
module.exports[`${role}GatewayToken`] = pulumi.secret(outputs.gatewayToken);
|
|
377
|
+
module.exports[`${role}InstanceId`] = outputs.instanceId;
|
|
378
|
+
module.exports[`${role}PublicIp`] = outputs.publicIp;
|
|
379
|
+
module.exports[`${role}SshPrivateKey`] = pulumi.secret(outputs.sshPrivateKey);
|
|
380
|
+
// Webhook URL for plugins that need it (derived from Tailscale Funnel public URL)
|
|
381
|
+
module.exports[`${role}WebhookUrl`] = outputs.tailscaleUrl.apply((url) => {
|
|
382
|
+
// Extract base URL (remove query params like ?token=...) and append webhook path
|
|
383
|
+
const baseUrl = url.split("?")[0].replace(/\/$/, "");
|
|
384
|
+
return `${baseUrl}/hooks/linear`;
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared VPC Component for Multi-Agent Deployments
|
|
4
|
+
*
|
|
5
|
+
* Creates a single VPC with subnet, internet gateway, and security group
|
|
6
|
+
* that can be shared across multiple OpenClaw agent instances for cost optimization.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.SharedVpc = void 0;
|
|
43
|
+
const pulumi = __importStar(require("@pulumi/pulumi"));
|
|
44
|
+
const aws = __importStar(require("@pulumi/aws"));
|
|
45
|
+
/**
|
|
46
|
+
* SharedVpc ComponentResource
|
|
47
|
+
*
|
|
48
|
+
* Creates shared networking infrastructure for multiple OpenClaw agents:
|
|
49
|
+
* - VPC with DNS support
|
|
50
|
+
* - Public subnet with auto-assign public IP
|
|
51
|
+
* - Internet gateway for outbound access
|
|
52
|
+
* - Route table with internet route
|
|
53
|
+
* - Security group allowing SSH and all outbound traffic
|
|
54
|
+
*/
|
|
55
|
+
class SharedVpc extends pulumi.ComponentResource {
|
|
56
|
+
/** VPC ID */
|
|
57
|
+
vpcId;
|
|
58
|
+
/** Subnet ID */
|
|
59
|
+
subnetId;
|
|
60
|
+
/** Security Group ID */
|
|
61
|
+
securityGroupId;
|
|
62
|
+
/** Internet Gateway ID */
|
|
63
|
+
internetGatewayId;
|
|
64
|
+
constructor(name, args = {}, opts) {
|
|
65
|
+
super("clawup:aws:SharedVpc", name, {}, opts);
|
|
66
|
+
const defaultResourceOptions = { parent: this };
|
|
67
|
+
const cidrBlock = args.cidrBlock ?? "10.0.0.0/16";
|
|
68
|
+
const subnetCidrBlock = args.subnetCidrBlock ?? "10.0.1.0/24";
|
|
69
|
+
const availabilityZone = args.availabilityZone ?? "us-east-1a";
|
|
70
|
+
const baseTags = args.tags ?? {};
|
|
71
|
+
// Create VPC
|
|
72
|
+
const vpc = new aws.ec2.Vpc(`${name}-vpc`, {
|
|
73
|
+
cidrBlock: cidrBlock,
|
|
74
|
+
enableDnsHostnames: true,
|
|
75
|
+
enableDnsSupport: true,
|
|
76
|
+
tags: pulumi.output(baseTags).apply((tags) => ({
|
|
77
|
+
...tags,
|
|
78
|
+
Name: `${name}-vpc`,
|
|
79
|
+
})),
|
|
80
|
+
}, defaultResourceOptions);
|
|
81
|
+
// Create Internet Gateway
|
|
82
|
+
const internetGateway = new aws.ec2.InternetGateway(`${name}-igw`, {
|
|
83
|
+
vpcId: vpc.id,
|
|
84
|
+
tags: pulumi.output(baseTags).apply((tags) => ({
|
|
85
|
+
...tags,
|
|
86
|
+
Name: `${name}-igw`,
|
|
87
|
+
})),
|
|
88
|
+
}, defaultResourceOptions);
|
|
89
|
+
// Create public subnet
|
|
90
|
+
const subnet = new aws.ec2.Subnet(`${name}-subnet`, {
|
|
91
|
+
vpcId: vpc.id,
|
|
92
|
+
cidrBlock: subnetCidrBlock,
|
|
93
|
+
availabilityZone: availabilityZone,
|
|
94
|
+
mapPublicIpOnLaunch: true,
|
|
95
|
+
tags: pulumi.output(baseTags).apply((tags) => ({
|
|
96
|
+
...tags,
|
|
97
|
+
Name: `${name}-subnet`,
|
|
98
|
+
})),
|
|
99
|
+
}, defaultResourceOptions);
|
|
100
|
+
// Create route table with internet route
|
|
101
|
+
const routeTable = new aws.ec2.RouteTable(`${name}-rt`, {
|
|
102
|
+
vpcId: vpc.id,
|
|
103
|
+
routes: [
|
|
104
|
+
{
|
|
105
|
+
cidrBlock: "0.0.0.0/0",
|
|
106
|
+
gatewayId: internetGateway.id,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
tags: pulumi.output(baseTags).apply((tags) => ({
|
|
110
|
+
...tags,
|
|
111
|
+
Name: `${name}-rt`,
|
|
112
|
+
})),
|
|
113
|
+
}, defaultResourceOptions);
|
|
114
|
+
// Associate route table with subnet
|
|
115
|
+
new aws.ec2.RouteTableAssociation(`${name}-rta`, {
|
|
116
|
+
subnetId: subnet.id,
|
|
117
|
+
routeTableId: routeTable.id,
|
|
118
|
+
}, defaultResourceOptions);
|
|
119
|
+
// Create security group
|
|
120
|
+
const securityGroup = new aws.ec2.SecurityGroup(`${name}-sg`, {
|
|
121
|
+
vpcId: vpc.id,
|
|
122
|
+
description: `Shared security group for ${name} agent fleet`,
|
|
123
|
+
// SSH is disabled by default — Tailscale is the primary access method.
|
|
124
|
+
// Pass allowedSshCidrs to enable SSH from specific IPs as a fallback.
|
|
125
|
+
ingress: pulumi
|
|
126
|
+
.output(args.allowedSshCidrs ?? [])
|
|
127
|
+
.apply((cidrs) => cidrs.length > 0
|
|
128
|
+
? [
|
|
129
|
+
{
|
|
130
|
+
description: "SSH access (restricted)",
|
|
131
|
+
fromPort: 22,
|
|
132
|
+
toPort: 22,
|
|
133
|
+
protocol: "tcp",
|
|
134
|
+
cidrBlocks: cidrs,
|
|
135
|
+
},
|
|
136
|
+
]
|
|
137
|
+
: []),
|
|
138
|
+
egress: [
|
|
139
|
+
{
|
|
140
|
+
description: "All outbound traffic",
|
|
141
|
+
fromPort: 0,
|
|
142
|
+
toPort: 0,
|
|
143
|
+
protocol: "-1",
|
|
144
|
+
cidrBlocks: ["0.0.0.0/0"],
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
tags: pulumi.output(baseTags).apply((tags) => ({
|
|
148
|
+
...tags,
|
|
149
|
+
Name: `${name}-sg`,
|
|
150
|
+
})),
|
|
151
|
+
}, defaultResourceOptions);
|
|
152
|
+
// Set outputs
|
|
153
|
+
this.vpcId = vpc.id;
|
|
154
|
+
this.subnetId = subnet.id;
|
|
155
|
+
this.securityGroupId = securityGroup.id;
|
|
156
|
+
this.internetGatewayId = internetGateway.id;
|
|
157
|
+
// Register outputs
|
|
158
|
+
this.registerOutputs({
|
|
159
|
+
vpcId: this.vpcId,
|
|
160
|
+
subnetId: this.subnetId,
|
|
161
|
+
securityGroupId: this.securityGroupId,
|
|
162
|
+
internetGatewayId: this.internetGatewayId,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.SharedVpc = SharedVpc;
|
|
167
|
+
//# sourceMappingURL=shared-vpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/schemas.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const schemas_1 = require("../schemas");
|
|
5
|
+
(0, vitest_1.describe)("AgentDefinitionSchema", () => {
|
|
6
|
+
const validAgent = {
|
|
7
|
+
name: "agent-pm",
|
|
8
|
+
displayName: "Juno",
|
|
9
|
+
role: "pm",
|
|
10
|
+
identity: "https://github.com/org/identities#pm",
|
|
11
|
+
volumeSize: 30,
|
|
12
|
+
};
|
|
13
|
+
(0, vitest_1.it)("accepts a valid agent definition", () => {
|
|
14
|
+
(0, vitest_1.expect)(() => schemas_1.AgentDefinitionSchema.parse(validAgent)).not.toThrow();
|
|
15
|
+
});
|
|
16
|
+
(0, vitest_1.it)("accepts agent with optional fields", () => {
|
|
17
|
+
const result = schemas_1.AgentDefinitionSchema.parse({
|
|
18
|
+
...validAgent,
|
|
19
|
+
identityVersion: "v1.0",
|
|
20
|
+
instanceType: "t3.large",
|
|
21
|
+
envVars: { FOO: "bar" },
|
|
22
|
+
plugins: { "openclaw-linear": { agentId: "agent-pm" } },
|
|
23
|
+
});
|
|
24
|
+
(0, vitest_1.expect)(result.plugins).toEqual({ "openclaw-linear": { agentId: "agent-pm" } });
|
|
25
|
+
});
|
|
26
|
+
(0, vitest_1.it)("rejects empty object with descriptive errors", () => {
|
|
27
|
+
const result = schemas_1.AgentDefinitionSchema.safeParse({});
|
|
28
|
+
(0, vitest_1.expect)(result.success).toBe(false);
|
|
29
|
+
if (!result.success) {
|
|
30
|
+
const paths = result.error.issues.map((i) => i.path[0]);
|
|
31
|
+
(0, vitest_1.expect)(paths).toContain("name");
|
|
32
|
+
(0, vitest_1.expect)(paths).toContain("displayName");
|
|
33
|
+
(0, vitest_1.expect)(paths).toContain("role");
|
|
34
|
+
(0, vitest_1.expect)(paths).toContain("identity");
|
|
35
|
+
(0, vitest_1.expect)(paths).toContain("volumeSize");
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.it)("rejects non-positive volumeSize", () => {
|
|
39
|
+
const result = schemas_1.AgentDefinitionSchema.safeParse({ ...validAgent, volumeSize: 0 });
|
|
40
|
+
(0, vitest_1.expect)(result.success).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.it)("rejects empty name", () => {
|
|
43
|
+
const result = schemas_1.AgentDefinitionSchema.safeParse({ ...validAgent, name: "" });
|
|
44
|
+
(0, vitest_1.expect)(result.success).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
(0, vitest_1.describe)("ClawupManifestSchema", () => {
|
|
48
|
+
const validManifest = {
|
|
49
|
+
stackName: "dev",
|
|
50
|
+
provider: "aws",
|
|
51
|
+
region: "us-east-1",
|
|
52
|
+
instanceType: "t3.medium",
|
|
53
|
+
ownerName: "Boss",
|
|
54
|
+
agents: [
|
|
55
|
+
{
|
|
56
|
+
name: "agent-pm",
|
|
57
|
+
displayName: "Juno",
|
|
58
|
+
role: "pm",
|
|
59
|
+
identity: "https://github.com/org/identities#pm",
|
|
60
|
+
volumeSize: 30,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
(0, vitest_1.it)("accepts a valid manifest", () => {
|
|
65
|
+
(0, vitest_1.expect)(() => schemas_1.ClawupManifestSchema.parse(validManifest)).not.toThrow();
|
|
66
|
+
});
|
|
67
|
+
(0, vitest_1.it)("rejects manifest with no agents", () => {
|
|
68
|
+
const result = schemas_1.ClawupManifestSchema.safeParse({ ...validManifest, agents: [] });
|
|
69
|
+
(0, vitest_1.expect)(result.success).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.it)("rejects invalid provider", () => {
|
|
72
|
+
const result = schemas_1.ClawupManifestSchema.safeParse({ ...validManifest, provider: "gcp" });
|
|
73
|
+
(0, vitest_1.expect)(result.success).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.describe)("IdentityManifestSchema", () => {
|
|
77
|
+
const validIdentity = {
|
|
78
|
+
name: "juno",
|
|
79
|
+
displayName: "Juno",
|
|
80
|
+
role: "pm",
|
|
81
|
+
emoji: "clipboard",
|
|
82
|
+
description: "Product manager agent",
|
|
83
|
+
volumeSize: 30,
|
|
84
|
+
skills: ["pm-queue-handler"],
|
|
85
|
+
templateVars: ["OWNER_NAME"],
|
|
86
|
+
};
|
|
87
|
+
(0, vitest_1.it)("accepts a valid identity manifest", () => {
|
|
88
|
+
(0, vitest_1.expect)(() => schemas_1.IdentityManifestSchema.parse(validIdentity)).not.toThrow();
|
|
89
|
+
});
|
|
90
|
+
(0, vitest_1.it)("rejects identity missing required fields", () => {
|
|
91
|
+
const result = schemas_1.IdentityManifestSchema.safeParse({ name: "only-name" });
|
|
92
|
+
(0, vitest_1.expect)(result.success).toBe(false);
|
|
93
|
+
if (!result.success) {
|
|
94
|
+
const paths = result.error.issues.map((i) => i.path[0]);
|
|
95
|
+
(0, vitest_1.expect)(paths).toContain("role");
|
|
96
|
+
(0, vitest_1.expect)(paths).toContain("volumeSize");
|
|
97
|
+
(0, vitest_1.expect)(paths).toContain("skills");
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
(0, vitest_1.it)("rejects non-positive volumeSize", () => {
|
|
101
|
+
const result = schemas_1.IdentityManifestSchema.safeParse({ ...validIdentity, volumeSize: 0 });
|
|
102
|
+
(0, vitest_1.expect)(result.success).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
(0, vitest_1.it)("validates plugins as array of non-empty strings", () => {
|
|
105
|
+
const result = schemas_1.IdentityManifestSchema.safeParse({
|
|
106
|
+
...validIdentity,
|
|
107
|
+
plugins: ["openclaw-linear", ""],
|
|
108
|
+
});
|
|
109
|
+
(0, vitest_1.expect)(result.success).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
(0, vitest_1.it)("accepts optional fields", () => {
|
|
112
|
+
const result = schemas_1.IdentityManifestSchema.parse({
|
|
113
|
+
...validIdentity,
|
|
114
|
+
model: "anthropic/claude-opus-4-6",
|
|
115
|
+
backupModel: "anthropic/claude-sonnet-4-5",
|
|
116
|
+
codingAgent: "claude-code",
|
|
117
|
+
deps: ["gh"],
|
|
118
|
+
pluginDefaults: { "openclaw-linear": { key: "value" } },
|
|
119
|
+
});
|
|
120
|
+
(0, vitest_1.expect)(result.model).toBe("anthropic/claude-opus-4-6");
|
|
121
|
+
(0, vitest_1.expect)(result.deps).toEqual(["gh"]);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=schemas.test.js.map
|