broods 0.3.0 → 0.4.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 +20 -5
- package/dist/account.d.ts +56 -53
- package/dist/account.js +19 -0
- package/dist/cli/index.js +410 -157
- package/dist/index.d.ts +217 -80
- package/dist/index.js +150 -24
- package/package.json +8 -8
package/dist/cli/index.js
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli/index.ts
|
|
5
|
-
import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, writeFile as
|
|
5
|
+
import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, writeFile as writeFile4 } from "fs/promises";
|
|
6
6
|
import { createHash as createHash2 } from "crypto";
|
|
7
7
|
import { basename as basename2, dirname as dirname2, join as join4, relative as relative2, resolve as resolve4 } from "path";
|
|
8
8
|
import { watch } from "fs";
|
|
9
9
|
import { spawn as spawn2 } from "child_process";
|
|
10
|
-
import { tmpdir } from "os";
|
|
10
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
11
11
|
import { performance } from "perf_hooks";
|
|
12
12
|
|
|
13
13
|
// src/manifest.ts
|
|
14
14
|
import { pathToFileURL } from "url";
|
|
15
15
|
import { createHash } from "crypto";
|
|
16
|
-
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
16
|
+
import { mkdtemp, readdir, readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
17
|
+
import { tmpdir } from "os";
|
|
17
18
|
import { basename, isAbsolute, join as join3, relative, resolve as resolve2 } from "path";
|
|
18
19
|
|
|
19
20
|
// src/config.ts
|
|
@@ -23,6 +24,9 @@ import { dirname, join } from "path";
|
|
|
23
24
|
var PROJECT_DIR = "broods";
|
|
24
25
|
var GENERATED_DIR = "_generated";
|
|
25
26
|
var USER_CONFIG_PATH = join(homedir(), ".broods", "config.json");
|
|
27
|
+
var BROODS_APEX_DOMAIN = "broods.app";
|
|
28
|
+
var DASHBOARD_HOST_PREFIX = "dashboard";
|
|
29
|
+
var GATEWAY_HOST_PREFIX = "gateway";
|
|
26
30
|
async function readStoredAuth() {
|
|
27
31
|
const envToken = process.env.BROODS_TOKEN;
|
|
28
32
|
const envConvexUrl = process.env.BROODS_BASE_URL;
|
|
@@ -50,6 +54,20 @@ async function writeStoredAuth(config) {
|
|
|
50
54
|
function stripTrailingSlash(value) {
|
|
51
55
|
return value.replace(/\/+$/, "");
|
|
52
56
|
}
|
|
57
|
+
function gatewayUrlForDashboard(dashboardUrl) {
|
|
58
|
+
try {
|
|
59
|
+
const url = new URL(dashboardUrl);
|
|
60
|
+
if (!url.hostname.startsWith(`${DASHBOARD_HOST_PREFIX}.`))
|
|
61
|
+
return;
|
|
62
|
+
const rest = url.hostname.slice(DASHBOARD_HOST_PREFIX.length + 1);
|
|
63
|
+
if (rest !== BROODS_APEX_DOMAIN && !rest.endsWith(`.${BROODS_APEX_DOMAIN}`))
|
|
64
|
+
return;
|
|
65
|
+
url.hostname = `${GATEWAY_HOST_PREFIX}.${rest}`;
|
|
66
|
+
return stripTrailingSlash(url.origin);
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
53
71
|
|
|
54
72
|
// src/runtime-config.ts
|
|
55
73
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
@@ -137,25 +155,27 @@ function readStoredAuthSync() {
|
|
|
137
155
|
var RESOURCE_MARKER = Symbol.for("broods.resource");
|
|
138
156
|
var CONFIG_MARKER = Symbol.for("broods.config");
|
|
139
157
|
var CHANNEL_MARKER = Symbol.for("broods.channel");
|
|
140
|
-
var
|
|
158
|
+
var ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
159
|
+
var env = new Proxy((name) => {
|
|
160
|
+
if (!ENV_NAME_PATTERN.test(name)) {
|
|
161
|
+
throw new Error("env name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");
|
|
162
|
+
}
|
|
141
163
|
return { __beeblastEnv: true, name };
|
|
142
164
|
}, {
|
|
143
165
|
get(target, property, receiver) {
|
|
144
|
-
if (typeof property === "string")
|
|
145
|
-
|
|
166
|
+
if (typeof property === "string" && ENV_NAME_PATTERN.test(property)) {
|
|
167
|
+
throw new Error(`env.${property} is not supported; use env("${property}")`);
|
|
168
|
+
}
|
|
146
169
|
return Reflect.get(target, property, receiver);
|
|
147
170
|
}
|
|
148
171
|
});
|
|
149
|
-
function defineResource(kind,
|
|
150
|
-
if (input.config === undefined) {
|
|
151
|
-
throw new Error(`Resource "${input.name}" must include config`);
|
|
152
|
-
}
|
|
172
|
+
function defineResource(kind, name, description, config) {
|
|
153
173
|
return {
|
|
154
174
|
[RESOURCE_MARKER]: true,
|
|
155
175
|
kind,
|
|
156
|
-
name
|
|
157
|
-
...
|
|
158
|
-
config
|
|
176
|
+
name,
|
|
177
|
+
...description ? { description } : {},
|
|
178
|
+
config
|
|
159
179
|
};
|
|
160
180
|
}
|
|
161
181
|
function defineChannel(type, config) {
|
|
@@ -193,25 +213,32 @@ function defineBroods(config) {
|
|
|
193
213
|
return { [CONFIG_MARKER]: true, config };
|
|
194
214
|
}
|
|
195
215
|
function defineAgent(input) {
|
|
196
|
-
|
|
216
|
+
const { name, description, ...config } = input;
|
|
217
|
+
return defineResource("agent", name, description, config);
|
|
197
218
|
}
|
|
198
219
|
function defineWorkspace(input) {
|
|
199
|
-
|
|
220
|
+
const { name, description, ...config } = input;
|
|
221
|
+
return defineResource("workspace", name, description, config);
|
|
200
222
|
}
|
|
201
223
|
function defineSandbox(input) {
|
|
202
|
-
|
|
224
|
+
const { name, description, ...config } = input;
|
|
225
|
+
return defineResource("sandbox", name, description, config);
|
|
203
226
|
}
|
|
204
227
|
function defineSkill(input) {
|
|
205
|
-
|
|
228
|
+
const { name, description, ...config } = input;
|
|
229
|
+
return defineResource("skill", name, description, config);
|
|
206
230
|
}
|
|
207
231
|
function defineTool(input) {
|
|
208
|
-
|
|
232
|
+
const { name, ...config } = input;
|
|
233
|
+
return defineResource("tool", name, undefined, config);
|
|
209
234
|
}
|
|
210
235
|
function definePolicy(input) {
|
|
211
|
-
|
|
236
|
+
const { name, description, ...config } = input;
|
|
237
|
+
return defineResource("policy", name, description, config);
|
|
212
238
|
}
|
|
213
239
|
function defineCron(input) {
|
|
214
|
-
|
|
240
|
+
const { name, description, ...config } = input;
|
|
241
|
+
return defineResource("cron", name, description, config);
|
|
215
242
|
}
|
|
216
243
|
function isResource(value) {
|
|
217
244
|
return Boolean(value && typeof value === "object" && value[RESOURCE_MARKER]);
|
|
@@ -224,8 +251,20 @@ function isBroodsConfig(value) {
|
|
|
224
251
|
}
|
|
225
252
|
|
|
226
253
|
// src/manifest.ts
|
|
227
|
-
var
|
|
228
|
-
|
|
254
|
+
var SDK_STUB_SOURCE = `const passthrough = (input) => input;
|
|
255
|
+
export const defineTool = passthrough;
|
|
256
|
+
export const defineAgent = passthrough;
|
|
257
|
+
export const defineWorkspace = passthrough;
|
|
258
|
+
export const defineSandbox = passthrough;
|
|
259
|
+
export const defineSkill = passthrough;
|
|
260
|
+
export const definePolicy = passthrough;
|
|
261
|
+
export const defineCron = passthrough;
|
|
262
|
+
export const defineBroods = passthrough;
|
|
263
|
+
export const env = (name) => ({ __beeblastEnv: true, name });
|
|
264
|
+
export default {};
|
|
265
|
+
`;
|
|
266
|
+
var MAX_BUNDLE_FILE_BYTES = 1e7;
|
|
267
|
+
var MAX_BUNDLE_TOTAL_BYTES = 20000000;
|
|
229
268
|
var MAX_BUNDLE_FILES = 200;
|
|
230
269
|
var SKIPPED_BUNDLE_DIRECTORIES = new Set(["node_modules", ".git"]);
|
|
231
270
|
var UNSAFE_BUNDLE_FILE_NAMES = [
|
|
@@ -252,7 +291,11 @@ async function compileProject(options = {}) {
|
|
|
252
291
|
const files = await listTypeScriptFiles(root);
|
|
253
292
|
const exports = await loadExports(files);
|
|
254
293
|
const config = await findConfig(exports, cwd, options.project);
|
|
255
|
-
const resourceExports = exports.filter((entry) => isResource(entry.value)).map((entry) => ({
|
|
294
|
+
const resourceExports = exports.filter((entry) => isResource(entry.value)).map((entry) => ({
|
|
295
|
+
exportName: entry.exportName,
|
|
296
|
+
file: entry.file,
|
|
297
|
+
resource: entry.value
|
|
298
|
+
}));
|
|
256
299
|
const resources = resourceExports.map((entry) => entry.resource);
|
|
257
300
|
assertUniqueResources(resources);
|
|
258
301
|
const channels = compileChannels(resourceExports, exports);
|
|
@@ -266,7 +309,7 @@ async function compileProject(options = {}) {
|
|
|
266
309
|
assertSupportedWorkspaceSandboxMounts(resources);
|
|
267
310
|
const resourceAliases = aliasesForResources(resourceExports);
|
|
268
311
|
const environment = resolveEnvironment(config, options.environment ?? (options.useRuntimeEnvironment === false ? undefined : process.env.BROODS_ENVIRONMENT), options.command ?? "dev");
|
|
269
|
-
const manifestResources = (await Promise.all(
|
|
312
|
+
const manifestResources = (await Promise.all(resourceExports.map((entry) => toManifestResources(entry, root)))).flat().sort((a, b) => `${a.kind}:${a.name}`.localeCompare(`${b.kind}:${b.name}`));
|
|
270
313
|
return {
|
|
271
314
|
config,
|
|
272
315
|
resources,
|
|
@@ -282,25 +325,25 @@ async function compileProject(options = {}) {
|
|
|
282
325
|
}
|
|
283
326
|
function collectEnvRefNames(manifest) {
|
|
284
327
|
const names = new Set;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
328
|
+
for (const resource of manifest.resources)
|
|
329
|
+
collectEnvRefNamesFromValue(resource.config, names);
|
|
330
|
+
return [...names].sort();
|
|
331
|
+
}
|
|
332
|
+
function collectEnvRefNamesFromValue(value, names) {
|
|
333
|
+
if (Array.isArray(value)) {
|
|
334
|
+
for (const entry of value)
|
|
335
|
+
collectEnvRefNamesFromValue(entry, names);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (value && typeof value === "object") {
|
|
339
|
+
const record = value;
|
|
340
|
+
if (record.__beeblastEnv === true && typeof record.name === "string") {
|
|
341
|
+
names.add(record.name);
|
|
289
342
|
return;
|
|
290
343
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (record.__beeblastEnv === true && typeof record.name === "string") {
|
|
294
|
-
names.add(record.name);
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
for (const entry of Object.values(record))
|
|
298
|
-
walk(entry);
|
|
299
|
-
}
|
|
344
|
+
for (const entry of Object.values(record))
|
|
345
|
+
collectEnvRefNamesFromValue(entry, names);
|
|
300
346
|
}
|
|
301
|
-
for (const resource of manifest.resources)
|
|
302
|
-
walk(resource.config);
|
|
303
|
-
return [...names].sort();
|
|
304
347
|
}
|
|
305
348
|
function resolveEnvironment(config, explicit, command) {
|
|
306
349
|
if (explicit)
|
|
@@ -333,7 +376,11 @@ async function loadExports(files) {
|
|
|
333
376
|
for (const file of files) {
|
|
334
377
|
const href = `${pathToFileURL(file).href}?t=${Date.now()}`;
|
|
335
378
|
const mod = await import(href);
|
|
336
|
-
values.push(...Object.entries(mod).map(([exportName, value]) => ({
|
|
379
|
+
values.push(...Object.entries(mod).map(([exportName, value]) => ({
|
|
380
|
+
exportName,
|
|
381
|
+
file,
|
|
382
|
+
value
|
|
383
|
+
})));
|
|
337
384
|
}
|
|
338
385
|
return values;
|
|
339
386
|
}
|
|
@@ -550,7 +597,7 @@ function supportsS3WorkspaceMount(sandbox) {
|
|
|
550
597
|
return Boolean(options && typeof options === "object" && !Array.isArray(options) && options.mountAwsS3Buckets === true);
|
|
551
598
|
}
|
|
552
599
|
function sandboxProvider(sandbox) {
|
|
553
|
-
return typeof sandbox.config.provider === "string" ? sandbox.config.provider : "
|
|
600
|
+
return typeof sandbox.config.provider === "string" ? sandbox.config.provider : "sandbox";
|
|
554
601
|
}
|
|
555
602
|
function assertUniqueResources(resources) {
|
|
556
603
|
const seen = new Set;
|
|
@@ -640,7 +687,8 @@ function capitalize(value) {
|
|
|
640
687
|
function isValidIdentifier(value) {
|
|
641
688
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
|
|
642
689
|
}
|
|
643
|
-
async function toManifestResources(
|
|
690
|
+
async function toManifestResources(entry, projectRoot) {
|
|
691
|
+
const resource = entry.resource;
|
|
644
692
|
if (resource.kind === "agent") {
|
|
645
693
|
const normalized = normalizeAgentConfig(resource, projectRoot);
|
|
646
694
|
return [
|
|
@@ -653,19 +701,22 @@ async function toManifestResources(resource, projectRoot) {
|
|
|
653
701
|
}
|
|
654
702
|
];
|
|
655
703
|
}
|
|
656
|
-
return [
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
704
|
+
return [
|
|
705
|
+
{
|
|
706
|
+
kind: resource.kind,
|
|
707
|
+
name: resource.name,
|
|
708
|
+
...resource.description ? { description: resource.description } : {},
|
|
709
|
+
config: await normalizeConfig(entry, projectRoot)
|
|
710
|
+
}
|
|
711
|
+
];
|
|
662
712
|
}
|
|
663
|
-
async function normalizeConfig(
|
|
713
|
+
async function normalizeConfig(entry, projectRoot) {
|
|
714
|
+
const resource = entry.resource;
|
|
664
715
|
if (resource.kind === "skill") {
|
|
665
716
|
return await normalizeSkillConfig(resource.config, projectRoot);
|
|
666
717
|
}
|
|
667
718
|
if (resource.kind === "tool") {
|
|
668
|
-
return await normalizeToolConfig(resource.config, projectRoot);
|
|
719
|
+
return await normalizeToolConfig(entry, resource.config, projectRoot);
|
|
669
720
|
}
|
|
670
721
|
if (resource.kind === "policy") {
|
|
671
722
|
const config = { ...resource.config };
|
|
@@ -679,14 +730,24 @@ async function normalizeConfig(resource, projectRoot) {
|
|
|
679
730
|
config.name = config.name ?? resource.name;
|
|
680
731
|
delete config.agent;
|
|
681
732
|
if (typeof config.input === "string") {
|
|
682
|
-
config.events = [
|
|
733
|
+
config.events = [
|
|
734
|
+
{ role: "user", content: [{ type: "text", text: config.input }] }
|
|
735
|
+
];
|
|
683
736
|
delete config.input;
|
|
684
737
|
}
|
|
685
738
|
return rewriteValues(config);
|
|
686
739
|
}
|
|
687
740
|
return rewriteValues(resource.config);
|
|
688
741
|
}
|
|
689
|
-
var KNOWN_PROVIDER_NAMES = [
|
|
742
|
+
var KNOWN_PROVIDER_NAMES = [
|
|
743
|
+
"google",
|
|
744
|
+
"openai",
|
|
745
|
+
"anthropic",
|
|
746
|
+
"bedrock",
|
|
747
|
+
"gateway",
|
|
748
|
+
"minimax",
|
|
749
|
+
"custom"
|
|
750
|
+
];
|
|
690
751
|
var KNOWN_PROVIDER_SETTING_KEYS = new Set([
|
|
691
752
|
"apiKey",
|
|
692
753
|
"base_url",
|
|
@@ -786,11 +847,13 @@ function normalizeInlineAgentHooks(agentName, hooks) {
|
|
|
786
847
|
if (typeof handler !== "function") {
|
|
787
848
|
throw new Error(`Agent "${agentName}" config.hooks.${name} must be a function`);
|
|
788
849
|
}
|
|
789
|
-
return [
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
850
|
+
return [
|
|
851
|
+
{
|
|
852
|
+
name,
|
|
853
|
+
event: INLINE_AGENT_HOOK_EVENTS[name],
|
|
854
|
+
source: toHookFunctionExpression(handler.toString())
|
|
855
|
+
}
|
|
856
|
+
];
|
|
794
857
|
});
|
|
795
858
|
if (entries.length === 0)
|
|
796
859
|
return;
|
|
@@ -904,26 +967,43 @@ async function normalizeSkillConfig(config, projectRoot) {
|
|
|
904
967
|
files
|
|
905
968
|
};
|
|
906
969
|
}
|
|
907
|
-
async function normalizeToolConfig(config, projectRoot) {
|
|
908
|
-
const
|
|
970
|
+
async function normalizeToolConfig(entry, config, projectRoot) {
|
|
971
|
+
const defaultConfigEnvRefs = new Set;
|
|
972
|
+
collectEnvRefNamesFromValue(config.defaultConfig, defaultConfigEnvRefs);
|
|
973
|
+
if (defaultConfigEnvRefs.size > 0) {
|
|
974
|
+
throw new Error(`Tool "${entry.resource.name}" defaultConfig cannot contain env("NAME") references; put environment values in the agent's tools.<tool>.config so they stay encrypted and environment-scoped`);
|
|
975
|
+
}
|
|
976
|
+
const bundlePath = config.path ? resolveContainedResourcePath(projectRoot, config.path, "Tool") : entry.file;
|
|
909
977
|
const manifestPath = relative(projectRoot, bundlePath).split("\\").join("/");
|
|
910
978
|
assertSafeBundlePath(manifestPath, "Tool");
|
|
911
979
|
const sourceSize = Buffer.byteLength(await readFile2(bundlePath));
|
|
912
980
|
if (sourceSize > MAX_BUNDLE_FILE_BYTES) {
|
|
913
981
|
throw new Error(`Tool source ${manifestPath} is too large (${sourceSize} bytes, max ${MAX_BUNDLE_FILE_BYTES})`);
|
|
914
982
|
}
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
minify: false
|
|
921
|
-
});
|
|
922
|
-
if (!build.success || build.outputs.length !== 1) {
|
|
923
|
-
const details = build.logs.map((entry) => entry.message).filter(Boolean).join("; ");
|
|
924
|
-
throw new Error(`Tool bundle ${manifestPath} failed to build${details ? `: ${details}` : ""}`);
|
|
983
|
+
const shimDir = await mkdtemp(join3(tmpdir(), "broods-tool-"));
|
|
984
|
+
const shimPath = join3(shimDir, "tool-adapter.mjs");
|
|
985
|
+
await writeFile2(shimPath, toolShimSource(bundlePath, config.path, entry), "utf8");
|
|
986
|
+
if (!config.path) {
|
|
987
|
+
await writeFile2(join3(shimDir, "broods-stub.mjs"), SDK_STUB_SOURCE, "utf8");
|
|
925
988
|
}
|
|
926
|
-
|
|
989
|
+
let bundle;
|
|
990
|
+
try {
|
|
991
|
+
const build = await Bun.build({
|
|
992
|
+
entrypoints: [shimPath],
|
|
993
|
+
target: "node",
|
|
994
|
+
format: "esm",
|
|
995
|
+
minify: false,
|
|
996
|
+
plugins: config.path ? [] : [sdkStubPlugin(shimDir)]
|
|
997
|
+
});
|
|
998
|
+
if (!build.success || build.outputs.length !== 1) {
|
|
999
|
+
const details = build.logs.map((entry2) => entry2.message).filter(Boolean).join("; ");
|
|
1000
|
+
throw new Error(`Tool bundle ${manifestPath} failed to build${details ? `: ${details}` : ""}`);
|
|
1001
|
+
}
|
|
1002
|
+
bundle = await build.outputs[0].text();
|
|
1003
|
+
} finally {
|
|
1004
|
+
await rm(shimDir, { recursive: true, force: true });
|
|
1005
|
+
}
|
|
1006
|
+
bundle = normalizeBundleComments(bundle, bundlePath, manifestPath);
|
|
927
1007
|
const bundleSize = Buffer.byteLength(bundle);
|
|
928
1008
|
if (bundleSize > MAX_BUNDLE_FILE_BYTES) {
|
|
929
1009
|
throw new Error(`Tool bundle ${manifestPath} is too large (${bundleSize} bytes, max ${MAX_BUNDLE_FILE_BYTES})`);
|
|
@@ -948,7 +1028,10 @@ function normalizeWorkspaceRef(entry, agentName) {
|
|
|
948
1028
|
if (typeof name !== "string") {
|
|
949
1029
|
throw new Error(`Agent ${agentName} workspace ref must be a defineWorkspace(...) resource or its name`);
|
|
950
1030
|
}
|
|
951
|
-
const normalized = {
|
|
1031
|
+
const normalized = {
|
|
1032
|
+
name,
|
|
1033
|
+
workspaceId: name
|
|
1034
|
+
};
|
|
952
1035
|
if ("sandbox" in entry) {
|
|
953
1036
|
const sandbox = entry.sandbox;
|
|
954
1037
|
normalized.sandbox = sandbox === null ? null : isResource(sandbox) ? sandbox.name : sandbox;
|
|
@@ -1048,21 +1131,49 @@ function contentTypeForPath(path) {
|
|
|
1048
1131
|
return "text/plain; charset=utf-8";
|
|
1049
1132
|
return "application/octet-stream";
|
|
1050
1133
|
}
|
|
1134
|
+
function sdkStubPlugin(shimDir) {
|
|
1135
|
+
const stub = join3(shimDir, "broods-stub.mjs");
|
|
1136
|
+
return {
|
|
1137
|
+
name: "broods-sdk-stub",
|
|
1138
|
+
setup(build) {
|
|
1139
|
+
build.onResolve({ filter: /^broods(\/.*)?$/ }, () => ({ path: stub }));
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
function toolShimSource(bundlePath, explicitPath, entry) {
|
|
1144
|
+
const from = JSON.stringify(bundlePath);
|
|
1145
|
+
if (explicitPath) {
|
|
1146
|
+
return `import __toolModule from ${from};
|
|
1147
|
+
const __impl = typeof __toolModule === "function" ? __toolModule() : __toolModule;
|
|
1148
|
+
export default { name: __impl.name, execute: (input, options) => __impl.execute(options.context, input) };
|
|
1149
|
+
`;
|
|
1150
|
+
}
|
|
1151
|
+
return `import { ${entry.exportName} as __tool } from ${from};
|
|
1152
|
+
export default { name: ${JSON.stringify(entry.resource.name)}, execute: (input, options) => __tool.execute(input, options) };
|
|
1153
|
+
`;
|
|
1154
|
+
}
|
|
1155
|
+
function escapeRegExp(value) {
|
|
1156
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1157
|
+
}
|
|
1158
|
+
function normalizeBundleComments(bundle, bundlePath, manifestPath) {
|
|
1159
|
+
const sourceComment = new RegExp(`^// .*${escapeRegExp(basename(bundlePath))}$`, "gm");
|
|
1160
|
+
return bundle.replace(/^\/\/ .*tool-adapter\.mjs$/gm, () => "// tool-adapter.mjs").replace(/^\/\/ .*broods-stub\.mjs$/gm, () => "// broods-stub.mjs").replace(sourceComment, () => `// ${manifestPath}`);
|
|
1161
|
+
}
|
|
1051
1162
|
function normalizeProjectName(name) {
|
|
1052
1163
|
return name.replace(/^@/, "").replace(/\//g, "-").replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1053
1164
|
}
|
|
1054
1165
|
|
|
1055
1166
|
// src/codegen.ts
|
|
1056
|
-
import { mkdir as mkdir2, writeFile as
|
|
1167
|
+
import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
1057
1168
|
import { resolve as resolve3 } from "path";
|
|
1058
1169
|
async function writeGeneratedFiles(manifest, ids, cwd = process.cwd(), aliases = {}, endpoint = null, channels = []) {
|
|
1059
1170
|
const dir = resolve3(cwd, PROJECT_DIR, GENERATED_DIR);
|
|
1060
1171
|
const scopedIds = idsForManifest(ids, manifest);
|
|
1061
1172
|
await mkdir2(dir, { recursive: true });
|
|
1062
|
-
await
|
|
1063
|
-
await
|
|
1064
|
-
await
|
|
1065
|
-
await
|
|
1173
|
+
await writeFile3(resolve3(dir, "ids.ts"), idsFile(scopedIds), "utf8");
|
|
1174
|
+
await writeFile3(resolve3(dir, "api.ts"), apiFile(manifest, scopedIds, aliases, endpoint, channels), "utf8");
|
|
1175
|
+
await writeFile3(resolve3(dir, "resources.ts"), resourcesFile(manifest), "utf8");
|
|
1176
|
+
await writeFile3(resolve3(dir, "dataModel.ts"), dataModelFile(), "utf8");
|
|
1066
1177
|
}
|
|
1067
1178
|
function idsForManifest(ids, manifest) {
|
|
1068
1179
|
const namesByKind = {
|
|
@@ -1169,7 +1280,9 @@ class BroodsSyncClient {
|
|
|
1169
1280
|
this.fetchImpl = options.fetch ?? fetch;
|
|
1170
1281
|
}
|
|
1171
1282
|
async getManifest(project, environment) {
|
|
1172
|
-
const response = await this.request(project, environment, "/manifest", {
|
|
1283
|
+
const response = await this.request(project, environment, "/manifest", {
|
|
1284
|
+
method: "GET"
|
|
1285
|
+
});
|
|
1173
1286
|
if (response.status === 404)
|
|
1174
1287
|
return null;
|
|
1175
1288
|
await assertOk(response, "Fetch manifest failed");
|
|
@@ -1185,7 +1298,9 @@ class BroodsSyncClient {
|
|
|
1185
1298
|
return await response.json();
|
|
1186
1299
|
}
|
|
1187
1300
|
async getRuntimeKey(project, environment) {
|
|
1188
|
-
const response = await this.request(project, environment, "/runtime-key", {
|
|
1301
|
+
const response = await this.request(project, environment, "/runtime-key", {
|
|
1302
|
+
method: "GET"
|
|
1303
|
+
});
|
|
1189
1304
|
if (response.status === 404)
|
|
1190
1305
|
return null;
|
|
1191
1306
|
await assertOk(response, "Fetch runtime key failed");
|
|
@@ -1209,7 +1324,9 @@ class BroodsSyncClient {
|
|
|
1209
1324
|
await assertOk(response, "Set environment variable failed");
|
|
1210
1325
|
}
|
|
1211
1326
|
async listEnv(project, environment) {
|
|
1212
|
-
const response = await this.request(project, environment, "/env", {
|
|
1327
|
+
const response = await this.request(project, environment, "/env", {
|
|
1328
|
+
method: "GET"
|
|
1329
|
+
});
|
|
1213
1330
|
await assertOk(response, "List environment variables failed");
|
|
1214
1331
|
const payload = await response.json();
|
|
1215
1332
|
return payload.variables ?? [];
|
|
@@ -1274,7 +1391,10 @@ class BroodsSyncClient {
|
|
|
1274
1391
|
}
|
|
1275
1392
|
}
|
|
1276
1393
|
function diffManifests(local, remote) {
|
|
1277
|
-
const remoteResources = new Map((remote?.resources ?? []).map((entry) => [
|
|
1394
|
+
const remoteResources = new Map((remote?.resources ?? []).map((entry) => [
|
|
1395
|
+
`${entry.kind}:${entry.name}`,
|
|
1396
|
+
entry
|
|
1397
|
+
]));
|
|
1278
1398
|
const localResources = new Map(local.resources.map((entry) => [`${entry.kind}:${entry.name}`, entry]));
|
|
1279
1399
|
const entries = [];
|
|
1280
1400
|
const unmatchedLocal = [];
|
|
@@ -1284,7 +1404,11 @@ function diffManifests(local, remote) {
|
|
|
1284
1404
|
if (!remoteResource) {
|
|
1285
1405
|
unmatchedLocal.push({ key, resource });
|
|
1286
1406
|
} else if (stableJson(snapshotResource(remoteResource)) !== stableJson(snapshotResource(resource))) {
|
|
1287
|
-
entries.push({
|
|
1407
|
+
entries.push({
|
|
1408
|
+
operation: "update",
|
|
1409
|
+
kind: resource.kind,
|
|
1410
|
+
name: resource.name
|
|
1411
|
+
});
|
|
1288
1412
|
}
|
|
1289
1413
|
}
|
|
1290
1414
|
for (const [key, resource] of remoteResources) {
|
|
@@ -1309,12 +1433,20 @@ function diffManifests(local, remote) {
|
|
|
1309
1433
|
}
|
|
1310
1434
|
for (const { key, resource } of unmatchedLocal) {
|
|
1311
1435
|
if (!renamedLocalKeys.has(key)) {
|
|
1312
|
-
entries.push({
|
|
1436
|
+
entries.push({
|
|
1437
|
+
operation: "create",
|
|
1438
|
+
kind: resource.kind,
|
|
1439
|
+
name: resource.name
|
|
1440
|
+
});
|
|
1313
1441
|
}
|
|
1314
1442
|
}
|
|
1315
1443
|
for (const { key, resource } of unmatchedRemote) {
|
|
1316
1444
|
if (!renamedRemoteKeys.has(key)) {
|
|
1317
|
-
entries.push({
|
|
1445
|
+
entries.push({
|
|
1446
|
+
operation: "delete",
|
|
1447
|
+
kind: resource.kind,
|
|
1448
|
+
name: resource.name
|
|
1449
|
+
});
|
|
1318
1450
|
}
|
|
1319
1451
|
}
|
|
1320
1452
|
return entries.sort((a, b) => diffSortKey(a).localeCompare(diffSortKey(b)));
|
|
@@ -1353,7 +1485,10 @@ function normalizeEnvRefs(value) {
|
|
|
1353
1485
|
if (record.__beeblastEnv === true && typeof record.name === "string") {
|
|
1354
1486
|
return `\${${record.name}}`;
|
|
1355
1487
|
}
|
|
1356
|
-
return Object.fromEntries(Object.entries(record).map(([key, entry]) => [
|
|
1488
|
+
return Object.fromEntries(Object.entries(record).map(([key, entry]) => [
|
|
1489
|
+
key,
|
|
1490
|
+
normalizeEnvRefs(entry)
|
|
1491
|
+
]));
|
|
1357
1492
|
}
|
|
1358
1493
|
return value;
|
|
1359
1494
|
}
|
|
@@ -1426,6 +1561,15 @@ async function* readSseStream(body) {
|
|
|
1426
1561
|
// src/client.ts
|
|
1427
1562
|
var DEFAULT_CORE_BASE_URL = "https://gateway.broods.app";
|
|
1428
1563
|
|
|
1564
|
+
class IngressAcceptedError extends Error {
|
|
1565
|
+
accepted;
|
|
1566
|
+
constructor(accepted) {
|
|
1567
|
+
super(`Ingress ${accepted.eventId} was accepted asynchronously with status ${accepted.status ?? "queued"}`);
|
|
1568
|
+
this.accepted = accepted;
|
|
1569
|
+
this.name = "IngressAcceptedError";
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1429
1573
|
class BroodsClient {
|
|
1430
1574
|
baseUrl;
|
|
1431
1575
|
apiKey;
|
|
@@ -1484,6 +1628,9 @@ class BroodsClient {
|
|
|
1484
1628
|
const response = await this.openStream(body, targetUrl);
|
|
1485
1629
|
if (!response.ok)
|
|
1486
1630
|
throw new Error(`Run failed: ${response.status} ${await response.text()}`);
|
|
1631
|
+
if (response.status === 202) {
|
|
1632
|
+
throw new IngressAcceptedError(normalizeAsyncAccepted(await response.json(), body));
|
|
1633
|
+
}
|
|
1487
1634
|
if (!response.body)
|
|
1488
1635
|
throw new Error("Run response has no body");
|
|
1489
1636
|
for await (const data of readSseStream(response.body)) {
|
|
@@ -1541,7 +1688,7 @@ class BroodsClient {
|
|
|
1541
1688
|
if (options.signal?.aborted)
|
|
1542
1689
|
throw new Error("Async status polling aborted.");
|
|
1543
1690
|
const payload = await this.getAsyncStatus(status, options);
|
|
1544
|
-
if (payload.status === "awaiting_approval" || payload.status === "completed" || payload.status === "failed" || payload.status === "not_found") {
|
|
1691
|
+
if (payload.status === "awaiting_approval" || payload.status === "completed" || payload.status === "failed" || payload.status === "expired" || payload.status === "not_found") {
|
|
1545
1692
|
return payload;
|
|
1546
1693
|
}
|
|
1547
1694
|
await sleep(intervalMs, options.signal);
|
|
@@ -1688,6 +1835,8 @@ function directRunBody(input, prefix) {
|
|
|
1688
1835
|
eventId,
|
|
1689
1836
|
conversationKey: input.conversationKey ?? eventId,
|
|
1690
1837
|
events: resolveRunEvents(input),
|
|
1838
|
+
...input.mode !== undefined ? { mode: input.mode } : {},
|
|
1839
|
+
...input.idempotencyKey !== undefined ? { idempotencyKey: input.idempotencyKey } : {},
|
|
1691
1840
|
...input.system !== undefined ? { system: input.system } : {},
|
|
1692
1841
|
...input.model !== undefined ? { model: input.model } : {}
|
|
1693
1842
|
};
|
|
@@ -1703,11 +1852,16 @@ function normalizeAsyncAccepted(payload, requestBody) {
|
|
|
1703
1852
|
const status = parseStatusUrl(statusUrl);
|
|
1704
1853
|
if (!status.statusId)
|
|
1705
1854
|
throw new Error("Async response statusUrl missing status id");
|
|
1855
|
+
const eventId = typeof payload.eventId === "string" ? payload.eventId : status.statusId;
|
|
1856
|
+
const acceptedStatus = payload.status;
|
|
1857
|
+
const requestedMode = payload.requestedMode;
|
|
1706
1858
|
return {
|
|
1707
1859
|
statusUrl,
|
|
1708
1860
|
statusId: status.statusId,
|
|
1709
|
-
eventId
|
|
1710
|
-
agentId: status.agentId ?? requestBody.agentId
|
|
1861
|
+
eventId,
|
|
1862
|
+
agentId: status.agentId ?? requestBody.agentId,
|
|
1863
|
+
...acceptedStatus === "accepted" || acceptedStatus === "queued" || acceptedStatus === "applied" || acceptedStatus === "processing" ? { status: acceptedStatus } : {},
|
|
1864
|
+
...requestedMode === "reject" || requestedMode === "followup" || requestedMode === "collect" || requestedMode === "steer" ? { requestedMode } : {}
|
|
1711
1865
|
};
|
|
1712
1866
|
}
|
|
1713
1867
|
function parseStatusUrl(statusUrl) {
|
|
@@ -1841,6 +1995,8 @@ class BroodsWebSocketClient {
|
|
|
1841
1995
|
events: resolveRunEvents(input),
|
|
1842
1996
|
sessionId: input.sessionId,
|
|
1843
1997
|
...input.eventId !== undefined ? { eventId: input.eventId } : {},
|
|
1998
|
+
...input.mode !== undefined ? { mode: input.mode } : {},
|
|
1999
|
+
...input.idempotencyKey !== undefined ? { idempotencyKey: input.idempotencyKey } : {},
|
|
1844
2000
|
...input.system !== undefined ? { system: input.system } : {},
|
|
1845
2001
|
...input.model !== undefined ? { model: input.model } : {}
|
|
1846
2002
|
}));
|
|
@@ -1851,16 +2007,17 @@ class BroodsWebSocketClient {
|
|
|
1851
2007
|
const payload = parseServerMessage(event.data);
|
|
1852
2008
|
if (!payload)
|
|
1853
2009
|
return;
|
|
1854
|
-
|
|
1855
|
-
|
|
2010
|
+
const effective = unwrapServerMessage(payload, handlers);
|
|
2011
|
+
handlers.onMessage?.(effective);
|
|
2012
|
+
switch (effective.type) {
|
|
1856
2013
|
case "meta":
|
|
1857
|
-
handlers.onMeta?.(
|
|
2014
|
+
handlers.onMeta?.(effective);
|
|
1858
2015
|
break;
|
|
1859
2016
|
case "done":
|
|
1860
2017
|
finish();
|
|
1861
2018
|
break;
|
|
1862
2019
|
case "error":
|
|
1863
|
-
fail(new Error(formatWireError(
|
|
2020
|
+
fail(new Error(formatWireError(effective.error)));
|
|
1864
2021
|
break;
|
|
1865
2022
|
}
|
|
1866
2023
|
};
|
|
@@ -1876,6 +2033,57 @@ class BroodsWebSocketClient {
|
|
|
1876
2033
|
};
|
|
1877
2034
|
return {
|
|
1878
2035
|
url,
|
|
2036
|
+
sendControl(message) {
|
|
2037
|
+
if (socket.readyState !== WS_OPEN)
|
|
2038
|
+
throw new Error("WebSocket is not open");
|
|
2039
|
+
socket.send(JSON.stringify({ type: "control", ...message }));
|
|
2040
|
+
},
|
|
2041
|
+
close
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
attach(input, handlers = {}) {
|
|
2045
|
+
const WebSocketImpl = this.resolveWebSocket();
|
|
2046
|
+
const url = this.buildUrl(input);
|
|
2047
|
+
const socket = new WebSocketImpl(url);
|
|
2048
|
+
let closed = false;
|
|
2049
|
+
const close = (code = 1000, reason = "client closed") => {
|
|
2050
|
+
if (closed)
|
|
2051
|
+
return;
|
|
2052
|
+
closed = true;
|
|
2053
|
+
if (socket.readyState === WS_OPEN || socket.readyState === WS_CONNECTING)
|
|
2054
|
+
socket.close(code, reason);
|
|
2055
|
+
};
|
|
2056
|
+
socket.onopen = () => socket.send(JSON.stringify({
|
|
2057
|
+
type: "attach",
|
|
2058
|
+
...input,
|
|
2059
|
+
signal: undefined,
|
|
2060
|
+
agent: undefined
|
|
2061
|
+
}));
|
|
2062
|
+
socket.onmessage = (event) => {
|
|
2063
|
+
if (typeof event.data !== "string")
|
|
2064
|
+
return;
|
|
2065
|
+
const payload = parseServerMessage(event.data);
|
|
2066
|
+
if (!payload)
|
|
2067
|
+
return;
|
|
2068
|
+
const effective = unwrapServerMessage(payload, handlers);
|
|
2069
|
+
handlers.onMessage?.(effective);
|
|
2070
|
+
if (effective.type === "done")
|
|
2071
|
+
handlers.onDone?.();
|
|
2072
|
+
if (effective.type === "error")
|
|
2073
|
+
handlers.onError?.(new Error(formatWireError(effective.error)));
|
|
2074
|
+
};
|
|
2075
|
+
socket.onerror = () => handlers.onError?.(webSocketAccessError(this.baseUrl));
|
|
2076
|
+
socket.onclose = (event) => {
|
|
2077
|
+
if (!closed && event.code !== 1000)
|
|
2078
|
+
handlers.onError?.(new Error(event.reason || "WebSocket connection closed."));
|
|
2079
|
+
};
|
|
2080
|
+
return {
|
|
2081
|
+
url,
|
|
2082
|
+
sendControl(message) {
|
|
2083
|
+
if (socket.readyState !== WS_OPEN)
|
|
2084
|
+
throw new Error("WebSocket is not open");
|
|
2085
|
+
socket.send(JSON.stringify({ type: "control", ...message }));
|
|
2086
|
+
},
|
|
1879
2087
|
close
|
|
1880
2088
|
};
|
|
1881
2089
|
}
|
|
@@ -1968,6 +2176,13 @@ function normalizeWebSocketServiceUrl(value) {
|
|
|
1968
2176
|
function webSocketAccessError(baseUrl) {
|
|
1969
2177
|
return new Error(`Cannot access the WebSocket service at ${baseUrl}.`);
|
|
1970
2178
|
}
|
|
2179
|
+
function unwrapServerMessage(payload, handlers) {
|
|
2180
|
+
if (payload.type !== "output")
|
|
2181
|
+
return payload;
|
|
2182
|
+
const output = payload;
|
|
2183
|
+
handlers.onOutput?.(output);
|
|
2184
|
+
return output.data;
|
|
2185
|
+
}
|
|
1971
2186
|
function parseServerMessage(data) {
|
|
1972
2187
|
try {
|
|
1973
2188
|
const value = JSON.parse(data);
|
|
@@ -2595,7 +2810,7 @@ function renderStreamPart(part, state) {
|
|
|
2595
2810
|
// package.json
|
|
2596
2811
|
var package_default = {
|
|
2597
2812
|
name: "broods",
|
|
2598
|
-
version: "0.
|
|
2813
|
+
version: "0.4.0",
|
|
2599
2814
|
description: "CLI + TypeScript SDK for the broods agent platform.",
|
|
2600
2815
|
type: "module",
|
|
2601
2816
|
license: "MIT",
|
|
@@ -2645,17 +2860,17 @@ var package_default = {
|
|
|
2645
2860
|
node: ">=22"
|
|
2646
2861
|
},
|
|
2647
2862
|
dependencies: {
|
|
2648
|
-
"@chat-adapter/discord": "^4.
|
|
2649
|
-
"@chat-adapter/github": "^4.
|
|
2650
|
-
"@chat-adapter/slack": "^4.
|
|
2651
|
-
"@chat-adapter/telegram": "^4.
|
|
2652
|
-
ai: "^7.0.
|
|
2863
|
+
"@chat-adapter/discord": "^4.35.0",
|
|
2864
|
+
"@chat-adapter/github": "^4.35.0",
|
|
2865
|
+
"@chat-adapter/slack": "^4.35.0",
|
|
2866
|
+
"@chat-adapter/telegram": "^4.35.0",
|
|
2867
|
+
ai: "^7.0.37"
|
|
2653
2868
|
},
|
|
2654
2869
|
devDependencies: {
|
|
2655
2870
|
"@types/bun": "^1.3.14",
|
|
2656
|
-
rollup: "^4.
|
|
2871
|
+
rollup: "^4.62.2",
|
|
2657
2872
|
typescript: "^6.0.3",
|
|
2658
|
-
"rollup-plugin-dts": "^6.
|
|
2873
|
+
"rollup-plugin-dts": "^6.4.1"
|
|
2659
2874
|
}
|
|
2660
2875
|
};
|
|
2661
2876
|
|
|
@@ -2676,7 +2891,7 @@ Commands:
|
|
|
2676
2891
|
init Create a broods/ project shell
|
|
2677
2892
|
login Authenticate with WorkOS through the dashboard
|
|
2678
2893
|
dev Watch + sync Development AND live-tail agent logs (like \`convex dev\`);
|
|
2679
|
-
confirms before deleting; auto-pushes env
|
|
2894
|
+
confirms before deleting; auto-pushes env("NAME") values from .env.local
|
|
2680
2895
|
dev --once Sync Development a single time and exit (no watch, no log stream)
|
|
2681
2896
|
diff Show local desired state vs remote state
|
|
2682
2897
|
deploy Sync Production once; writes BROODS_API_KEY to .env.local
|
|
@@ -2686,7 +2901,7 @@ Commands:
|
|
|
2686
2901
|
env list List environment variable names (values stay hidden)
|
|
2687
2902
|
env rm <name> Remove an environment variable
|
|
2688
2903
|
stream Stream live logs for the whole project/environment (Ctrl+C to stop)
|
|
2689
|
-
logs Backfill recent logs then live-tail; default 100 lines
|
|
2904
|
+
logs Backfill recent logs then live-tail; all levels, default 100 lines
|
|
2690
2905
|
(--errors / --level warn filter to WARN+; -n/--limit <n> changes backfill size)
|
|
2691
2906
|
agent list List the agents in the current project/environment scope
|
|
2692
2907
|
agent get <name> Show an agent's resources (model, sandbox, workspaces, tools, channels)
|
|
@@ -2761,8 +2976,10 @@ async function init(args) {
|
|
|
2761
2976
|
await writeStarter(resolve4(root, ".gitignore"), `_generated
|
|
2762
2977
|
.cache
|
|
2763
2978
|
`, force);
|
|
2979
|
+
const dashboardUrl = optionValue(args, "--dashboard-url") ?? DEFAULT_DASHBOARD_URL;
|
|
2764
2980
|
await writeLocalEnvDefaults({
|
|
2765
|
-
dashboardUrl
|
|
2981
|
+
dashboardUrl,
|
|
2982
|
+
baseUrl: gatewayUrlForDashboard(dashboardUrl),
|
|
2766
2983
|
project: optionValue(args, "--project") ?? inferProjectName(process.cwd()),
|
|
2767
2984
|
environment: optionValue(args, "--env") ?? "development",
|
|
2768
2985
|
region: optionValue(args, "--region") ?? DEFAULT_SERVICE_REGION,
|
|
@@ -2782,6 +2999,7 @@ async function login(args) {
|
|
|
2782
2999
|
const environment = optionValue(args, "--env") ?? process.env.BROODS_ENVIRONMENT ?? "development";
|
|
2783
3000
|
await writeLocalEnvDefaults({
|
|
2784
3001
|
dashboardUrl: auth.dashboardUrl ?? dashboardUrl,
|
|
3002
|
+
baseUrl: auth.baseUrl,
|
|
2785
3003
|
project,
|
|
2786
3004
|
environment,
|
|
2787
3005
|
region: optionValue(args, "--region") ?? process.env.BROODS_REGION ?? DEFAULT_SERVICE_REGION,
|
|
@@ -2816,7 +3034,10 @@ async function diff(args) {
|
|
|
2816
3034
|
command: "dev"
|
|
2817
3035
|
});
|
|
2818
3036
|
const auth = await requireAuth(optionValue(args, "--base-url") ?? config.baseUrl);
|
|
2819
|
-
const client = new BroodsSyncClient({
|
|
3037
|
+
const client = new BroodsSyncClient({
|
|
3038
|
+
baseUrl: auth.baseUrl,
|
|
3039
|
+
token: auth.token
|
|
3040
|
+
});
|
|
2820
3041
|
const remote = await client.getManifest(manifest.project, manifest.environment);
|
|
2821
3042
|
printDiffEntries(diffManifests(manifest, remote?.manifest ?? null));
|
|
2822
3043
|
}
|
|
@@ -2828,7 +3049,10 @@ async function deploy(args) {
|
|
|
2828
3049
|
useRuntimeEnvironment: false
|
|
2829
3050
|
});
|
|
2830
3051
|
const auth = await requireAuth(optionValue(args, "--base-url") ?? config.baseUrl);
|
|
2831
|
-
const client = new BroodsSyncClient({
|
|
3052
|
+
const client = new BroodsSyncClient({
|
|
3053
|
+
baseUrl: auth.baseUrl,
|
|
3054
|
+
token: auth.token
|
|
3055
|
+
});
|
|
2832
3056
|
const result = await client.putManifest(manifest, hasFlag(args, "--prune"), hasFlag(args, "--rotate-key"));
|
|
2833
3057
|
await writeGeneratedFiles(manifest, result.ids, process.cwd(), resourceAliases, result.deployment, channels);
|
|
2834
3058
|
await ensureGitIgnore();
|
|
@@ -2869,7 +3093,7 @@ async function dev(args) {
|
|
|
2869
3093
|
printReadyLine(performance.now() - start);
|
|
2870
3094
|
return;
|
|
2871
3095
|
}
|
|
2872
|
-
const declinedFile = join4(
|
|
3096
|
+
const declinedFile = join4(tmpdir2(), `broods-declined-${process.pid}.txt`);
|
|
2873
3097
|
const childEnv = {
|
|
2874
3098
|
...process.env,
|
|
2875
3099
|
BROODS_DECLINED_FILE: declinedFile,
|
|
@@ -2932,7 +3156,12 @@ async function streamDevLogs(args, signal) {
|
|
|
2932
3156
|
}
|
|
2933
3157
|
const minLevel = resolveMinLevel(args);
|
|
2934
3158
|
try {
|
|
2935
|
-
for await (const entry of subscribeObservabilityLogs({
|
|
3159
|
+
for await (const entry of subscribeObservabilityLogs({
|
|
3160
|
+
baseUrl: creds.baseUrl,
|
|
3161
|
+
apiKey: creds.apiKey,
|
|
3162
|
+
project,
|
|
3163
|
+
environment
|
|
3164
|
+
}, { backfill: 0, minLevel, signal })) {
|
|
2936
3165
|
console.log(formatObservabilityEntry(entry));
|
|
2937
3166
|
}
|
|
2938
3167
|
} catch (error) {
|
|
@@ -2964,6 +3193,7 @@ async function ensureLocalDevDefaults(args) {
|
|
|
2964
3193
|
const values = parseEnv2(current);
|
|
2965
3194
|
const missing = [
|
|
2966
3195
|
"BROODS_DASHBOARD_URL",
|
|
3196
|
+
"BROODS_BASE_URL",
|
|
2967
3197
|
"BROODS_PROJECT",
|
|
2968
3198
|
"BROODS_ENVIRONMENT",
|
|
2969
3199
|
"BROODS_REGION"
|
|
@@ -2980,7 +3210,10 @@ async function ensureLocalDevDefaults(args) {
|
|
|
2980
3210
|
let region = optionValue(args, "--region") ?? process.env.BROODS_REGION ?? DEFAULT_SERVICE_REGION;
|
|
2981
3211
|
if (process.stdin.isTTY && needsProject) {
|
|
2982
3212
|
const auth = await requireAuthOrLogin(dashboardUrl);
|
|
2983
|
-
const client = new BroodsSyncClient({
|
|
3213
|
+
const client = new BroodsSyncClient({
|
|
3214
|
+
baseUrl: auth.baseUrl,
|
|
3215
|
+
token: auth.token
|
|
3216
|
+
});
|
|
2984
3217
|
const context = await getOnboardingContextOrFallback(client, auth);
|
|
2985
3218
|
const selectedContext = await selectOnboardingOrg(client, context);
|
|
2986
3219
|
project = await selectOnboardingProject(selectedContext, project);
|
|
@@ -2997,6 +3230,7 @@ async function ensureLocalDevDefaults(args) {
|
|
|
2997
3230
|
}
|
|
2998
3231
|
await writeLocalEnvDefaults({
|
|
2999
3232
|
dashboardUrl,
|
|
3233
|
+
baseUrl: runtime.baseUrl ?? gatewayUrlForDashboard(dashboardUrl),
|
|
3000
3234
|
project,
|
|
3001
3235
|
environment,
|
|
3002
3236
|
region,
|
|
@@ -3022,13 +3256,15 @@ async function getOnboardingContextOrFallback(client, auth) {
|
|
|
3022
3256
|
printWarning("CLI onboarding endpoint is not available yet; using the org from the current login.");
|
|
3023
3257
|
return {
|
|
3024
3258
|
currentOrgId: auth.org.id,
|
|
3025
|
-
orgs: [
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3259
|
+
orgs: [
|
|
3260
|
+
{
|
|
3261
|
+
id: auth.org.id,
|
|
3262
|
+
name: auth.org.name,
|
|
3263
|
+
slug: auth.org.slug,
|
|
3264
|
+
role: "admin",
|
|
3265
|
+
accountStatus: "active"
|
|
3266
|
+
}
|
|
3267
|
+
],
|
|
3032
3268
|
projects: []
|
|
3033
3269
|
};
|
|
3034
3270
|
}
|
|
@@ -3057,7 +3293,11 @@ async function selectOnboardingProject(context, inferred) {
|
|
|
3057
3293
|
if (context.projects.length === 0) {
|
|
3058
3294
|
return promptText("Project name", inferred);
|
|
3059
3295
|
}
|
|
3060
|
-
const createNew = {
|
|
3296
|
+
const createNew = {
|
|
3297
|
+
kind: "create",
|
|
3298
|
+
name: defaultProjectName(context, inferred),
|
|
3299
|
+
slug: ""
|
|
3300
|
+
};
|
|
3061
3301
|
const choices = [
|
|
3062
3302
|
...context.projects,
|
|
3063
3303
|
createNew
|
|
@@ -3068,12 +3308,12 @@ async function selectOnboardingProject(context, inferred) {
|
|
|
3068
3308
|
}
|
|
3069
3309
|
return selected.name;
|
|
3070
3310
|
}
|
|
3071
|
-
function runSyncChild(args,
|
|
3311
|
+
function runSyncChild(args, env2) {
|
|
3072
3312
|
return new Promise((resolvePromise, reject) => {
|
|
3073
3313
|
const entryPoint = process.argv[1] ?? "";
|
|
3074
3314
|
const child = spawn2(process.execPath, [entryPoint, "dev", "--once", ...args], {
|
|
3075
3315
|
stdio: "inherit",
|
|
3076
|
-
env:
|
|
3316
|
+
env: env2
|
|
3077
3317
|
});
|
|
3078
3318
|
child.on("error", reject);
|
|
3079
3319
|
child.on("exit", (code, signal) => {
|
|
@@ -3092,7 +3332,10 @@ async function syncDev(args) {
|
|
|
3092
3332
|
command: "dev"
|
|
3093
3333
|
});
|
|
3094
3334
|
const auth = await requireAuth(optionValue(args, "--base-url") ?? config.baseUrl);
|
|
3095
|
-
const client = new BroodsSyncClient({
|
|
3335
|
+
const client = new BroodsSyncClient({
|
|
3336
|
+
baseUrl: auth.baseUrl,
|
|
3337
|
+
token: auth.token
|
|
3338
|
+
});
|
|
3096
3339
|
const remote = await client.getManifest(manifest.project, manifest.environment);
|
|
3097
3340
|
const diff2 = diffManifests(manifest, remote?.manifest ?? null);
|
|
3098
3341
|
printDiffEntries(diff2.filter((entry) => entry.operation !== "delete"));
|
|
@@ -3192,7 +3435,7 @@ async function loadEnvSyncCache() {
|
|
|
3192
3435
|
async function saveEnvSyncCache(cache) {
|
|
3193
3436
|
const path = envSyncCachePath();
|
|
3194
3437
|
await mkdir3(resolve4(path, ".."), { recursive: true });
|
|
3195
|
-
await
|
|
3438
|
+
await writeFile4(path, `${JSON.stringify(cache, null, 2)}
|
|
3196
3439
|
`, "utf8");
|
|
3197
3440
|
}
|
|
3198
3441
|
async function rememberEnvSyncValue(scopeKey, project, environment, name, value) {
|
|
@@ -3233,7 +3476,7 @@ async function rememberDeclinedDeletes(keys) {
|
|
|
3233
3476
|
const path = process.env.BROODS_DECLINED_FILE;
|
|
3234
3477
|
if (!path || keys.length === 0)
|
|
3235
3478
|
return;
|
|
3236
|
-
await
|
|
3479
|
+
await writeFile4(path, `${keys.join(`
|
|
3237
3480
|
`)}
|
|
3238
3481
|
`, { flag: "a" });
|
|
3239
3482
|
}
|
|
@@ -3241,7 +3484,7 @@ async function clearDeclinedDeletes() {
|
|
|
3241
3484
|
const path = process.env.BROODS_DECLINED_FILE;
|
|
3242
3485
|
if (!path)
|
|
3243
3486
|
return;
|
|
3244
|
-
await
|
|
3487
|
+
await writeFile4(path, "", "utf8");
|
|
3245
3488
|
}
|
|
3246
3489
|
async function envCommand(args) {
|
|
3247
3490
|
const subcommand = args[0];
|
|
@@ -3259,7 +3502,10 @@ async function envCommand(args) {
|
|
|
3259
3502
|
command: "dev"
|
|
3260
3503
|
});
|
|
3261
3504
|
const auth = await requireAuth(optionValue(args, "--base-url") ?? config.baseUrl);
|
|
3262
|
-
const client = new BroodsSyncClient({
|
|
3505
|
+
const client = new BroodsSyncClient({
|
|
3506
|
+
baseUrl: auth.baseUrl,
|
|
3507
|
+
token: auth.token
|
|
3508
|
+
});
|
|
3263
3509
|
const scopeKey = envSyncScopeKey(auth.baseUrl, auth.token);
|
|
3264
3510
|
const target = `${manifest.project}/${manifest.environment}`;
|
|
3265
3511
|
if (isList) {
|
|
@@ -3308,7 +3554,7 @@ function resolveMinLevel(args) {
|
|
|
3308
3554
|
return "WARN";
|
|
3309
3555
|
const raw = optionValue(args, "--level");
|
|
3310
3556
|
if (!raw)
|
|
3311
|
-
return
|
|
3557
|
+
return;
|
|
3312
3558
|
const upper = raw.toUpperCase();
|
|
3313
3559
|
if (upper === "WARN" || upper === "WARNING")
|
|
3314
3560
|
return "WARN";
|
|
@@ -3402,7 +3648,10 @@ async function loadAgentsWithIds(args) {
|
|
|
3402
3648
|
command: "dev"
|
|
3403
3649
|
});
|
|
3404
3650
|
const auth = await requireAuth(optionValue(args, "--base-url") ?? config.baseUrl);
|
|
3405
|
-
const remote = await new BroodsSyncClient({
|
|
3651
|
+
const remote = await new BroodsSyncClient({
|
|
3652
|
+
baseUrl: auth.baseUrl,
|
|
3653
|
+
token: auth.token
|
|
3654
|
+
}).getManifest(manifest.project, manifest.environment);
|
|
3406
3655
|
const agents = manifest.resources.filter((resource) => resource.kind === "agent").map((resource) => ({
|
|
3407
3656
|
name: resource.name,
|
|
3408
3657
|
config: resource.config ?? {},
|
|
@@ -3479,18 +3728,27 @@ async function run(args) {
|
|
|
3479
3728
|
const agent = manifest.resources.find((resource) => resource.kind === "agent" && resource.name === agentName);
|
|
3480
3729
|
if (!agent)
|
|
3481
3730
|
throw new Error(`Unknown local agent: ${agentName}`);
|
|
3482
|
-
const remote = await new BroodsSyncClient({
|
|
3731
|
+
const remote = await new BroodsSyncClient({
|
|
3732
|
+
baseUrl: auth.baseUrl,
|
|
3733
|
+
token: auth.token
|
|
3734
|
+
}).getManifest(manifest.project, manifest.environment);
|
|
3483
3735
|
const agentId = remote?.ids.agents[agentName];
|
|
3484
3736
|
if (!agentId)
|
|
3485
3737
|
throw new Error(`Agent ${agentName} is not synced yet. Run broods dev --once or broods deploy first.`);
|
|
3486
|
-
const runtimeKey = await new BroodsSyncClient({
|
|
3738
|
+
const runtimeKey = await new BroodsSyncClient({
|
|
3739
|
+
baseUrl: auth.baseUrl,
|
|
3740
|
+
token: auth.token
|
|
3741
|
+
}).getRuntimeKey(manifest.project, manifest.environment).catch(() => null);
|
|
3487
3742
|
if (runtimeKey?.apiKey) {
|
|
3488
3743
|
await writeEnvValue("BROODS_API_KEY", runtimeKey.apiKey);
|
|
3489
3744
|
}
|
|
3490
3745
|
if (agent.config.publicAccess !== true) {
|
|
3491
3746
|
printWarning(`\u26A0 Agent "${agentName}" does not set publicAccess: true. The public endpoint is secured by default; ` + "if the deployed agent has not enabled it, this run will be refused.");
|
|
3492
3747
|
}
|
|
3493
|
-
const client = new BroodsClient(
|
|
3748
|
+
const client = new BroodsClient({
|
|
3749
|
+
baseUrl: optionValue(args, "--base-url") ?? process.env.BROODS_BASE_URL ?? process.env.BROODS_HOST ?? auth.baseUrl,
|
|
3750
|
+
...runtimeKey?.apiKey ? { apiKey: runtimeKey.apiKey } : {}
|
|
3751
|
+
});
|
|
3494
3752
|
const state = createRenderState();
|
|
3495
3753
|
try {
|
|
3496
3754
|
for await (const part of client.stream({
|
|
@@ -3517,7 +3775,7 @@ async function run(args) {
|
|
|
3517
3775
|
}
|
|
3518
3776
|
async function writeStarter(path, contents, force) {
|
|
3519
3777
|
try {
|
|
3520
|
-
await
|
|
3778
|
+
await writeFile4(path, contents, { flag: force ? "w" : "wx" });
|
|
3521
3779
|
} catch (error) {
|
|
3522
3780
|
if (error.code === "EEXIST")
|
|
3523
3781
|
return;
|
|
@@ -3537,7 +3795,7 @@ async function ensureGitIgnore() {
|
|
|
3537
3795
|
` : missing.join(`
|
|
3538
3796
|
`) + `
|
|
3539
3797
|
`;
|
|
3540
|
-
await
|
|
3798
|
+
await writeFile4(path, body, "utf8");
|
|
3541
3799
|
}
|
|
3542
3800
|
async function writeLocalEnvDefaults(options) {
|
|
3543
3801
|
const path = resolve4(process.cwd(), ".env.local");
|
|
@@ -3545,14 +3803,13 @@ async function writeLocalEnvDefaults(options) {
|
|
|
3545
3803
|
const values = parseEnv2(current);
|
|
3546
3804
|
const nextValues = {
|
|
3547
3805
|
BROODS_DASHBOARD_URL: options.dashboardUrl,
|
|
3806
|
+
...options.baseUrl ? { BROODS_BASE_URL: options.baseUrl } : {},
|
|
3548
3807
|
BROODS_PROJECT: options.project,
|
|
3549
3808
|
BROODS_ENVIRONMENT: options.environment,
|
|
3550
3809
|
BROODS_REGION: options.region
|
|
3551
3810
|
};
|
|
3552
3811
|
const lines = current ? current.replace(/\n?$/, `
|
|
3553
|
-
`).split(/\n/) : [
|
|
3554
|
-
"# Local broods CLI settings. Tokens are stored outside the repo."
|
|
3555
|
-
];
|
|
3812
|
+
`).split(/\n/) : ["# Local broods CLI settings. Tokens are stored outside the repo."];
|
|
3556
3813
|
let changed = false;
|
|
3557
3814
|
for (const [key, value] of Object.entries(nextValues)) {
|
|
3558
3815
|
if (values[key] !== undefined && !options.force)
|
|
@@ -3571,7 +3828,7 @@ async function writeLocalEnvDefaults(options) {
|
|
|
3571
3828
|
const body = `${lines.filter((line, index, all) => !(line === "" && index === all.length - 1)).join(`
|
|
3572
3829
|
`)}
|
|
3573
3830
|
`;
|
|
3574
|
-
await
|
|
3831
|
+
await writeFile4(path, body, "utf8");
|
|
3575
3832
|
}
|
|
3576
3833
|
async function writeEnvValue(key, value) {
|
|
3577
3834
|
const path = resolve4(process.cwd(), ".env.local");
|
|
@@ -3587,7 +3844,7 @@ async function writeEnvValue(key, value) {
|
|
|
3587
3844
|
const body = `${lines.filter((line, i, all) => !(line === "" && i === all.length - 1)).join(`
|
|
3588
3845
|
`)}
|
|
3589
3846
|
`;
|
|
3590
|
-
await
|
|
3847
|
+
await writeFile4(path, body, "utf8");
|
|
3591
3848
|
}
|
|
3592
3849
|
async function readTextIfExists(path) {
|
|
3593
3850
|
try {
|
|
@@ -3653,33 +3910,29 @@ function starterAgent() {
|
|
|
3653
3910
|
` + `// A Lambda sandbox: a fresh, ephemeral bash environment created per run.
|
|
3654
3911
|
` + `export const lambdaSandbox = defineSandbox({
|
|
3655
3912
|
` + ` name: "lambda-sandbox",
|
|
3656
|
-
` + `
|
|
3657
|
-
` + `
|
|
3658
|
-
` + `
|
|
3659
|
-
` + `
|
|
3660
|
-
` + ` timeout: 60,
|
|
3661
|
-
` + ` },
|
|
3913
|
+
` + ` provider: "lambda",
|
|
3914
|
+
` + ` network: { mode: "deny-all" },
|
|
3915
|
+
` + ` permissionMode: "bypass",
|
|
3916
|
+
` + ` timeout: 60,
|
|
3662
3917
|
` + `});
|
|
3663
3918
|
|
|
3664
3919
|
` + `export const myAgent = defineAgent({
|
|
3665
3920
|
` + ` name: "my-agent",
|
|
3666
|
-
` + `
|
|
3667
|
-
` + `
|
|
3668
|
-
` + `
|
|
3669
|
-
` + `
|
|
3670
|
-
` + `
|
|
3671
|
-
` + `
|
|
3672
|
-
` + `
|
|
3673
|
-
` + `
|
|
3674
|
-
` + `
|
|
3675
|
-
` + ` system: "You are a helpful assistant.",
|
|
3676
|
-
` + ` },
|
|
3677
|
-
` + ` sandbox: lambdaSandbox,
|
|
3678
|
-
` + ` // Expose the public runtime endpoint (SSE/WebSocket) so the API key and
|
|
3679
|
-
` + ` // \`broods run\` can reach this agent. Off by default \u2014 secured: a
|
|
3680
|
-
` + ` // private agent is only reachable via internal endpoints or channel webhooks.
|
|
3681
|
-
` + ` publicAccess: true,
|
|
3921
|
+
` + ` provider: {
|
|
3922
|
+
` + ` openai: { apiKey: env("OPENAI_API_KEY") },
|
|
3923
|
+
` + ` },
|
|
3924
|
+
` + ` model: {
|
|
3925
|
+
` + ` provider: "openai",
|
|
3926
|
+
` + ` modelId: "gpt-5.5",
|
|
3927
|
+
` + ` },
|
|
3928
|
+
` + ` agent: {
|
|
3929
|
+
` + ` system: "You are a helpful assistant.",
|
|
3682
3930
|
` + ` },
|
|
3931
|
+
` + ` sandbox: lambdaSandbox,
|
|
3932
|
+
` + ` // Expose the public runtime endpoint (SSE/WebSocket) so the API key and
|
|
3933
|
+
` + ` // \`broods run\` can reach this agent. Off by default \u2014 secured: a
|
|
3934
|
+
` + ` // private agent is only reachable via internal endpoints or channel webhooks.
|
|
3935
|
+
` + ` publicAccess: true,
|
|
3683
3936
|
` + `});
|
|
3684
3937
|
`;
|
|
3685
3938
|
}
|