spawnfile 0.1.3 → 0.1.5
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 +2 -0
- package/dist/cli/lifecycleCommands.d.ts +3 -0
- package/dist/cli/lifecycleCommands.js +80 -0
- package/dist/cli/runCli.d.ts +2 -1
- package/dist/cli/runCli.js +5 -47
- package/dist/compiler/buildCompilePlan.js +12 -202
- package/dist/compiler/buildCompilePlanRuntime.d.ts +6 -1
- package/dist/compiler/buildCompilePlanRuntime.js +9 -0
- package/dist/compiler/buildCompilePlanTeams.js +16 -10
- package/dist/compiler/buildCompilePlanTraversal.d.ts +16 -0
- package/dist/compiler/buildCompilePlanTraversal.js +214 -0
- package/dist/compiler/buildCompilePlanTraversalHelpers.d.ts +18 -0
- package/dist/compiler/buildCompilePlanTraversalHelpers.js +22 -0
- package/dist/compiler/compilePlanHelpers.d.ts +3 -1
- package/dist/compiler/compilePlanHelpers.js +37 -1
- package/dist/compiler/compileProject.js +14 -0
- package/dist/compiler/containerArtifacts.js +18 -3
- package/dist/compiler/containerArtifactsPlans.js +32 -0
- package/dist/compiler/containerArtifactsRender.js +86 -3
- package/dist/compiler/containerArtifactsTypes.d.ts +7 -3
- package/dist/compiler/containerEntrypointRender.d.ts +1 -1
- package/dist/compiler/containerEntrypointRender.js +37 -27
- package/dist/compiler/containerTargetResources.d.ts +4 -0
- package/dist/compiler/containerTargetResources.js +54 -0
- package/dist/compiler/containerWorkspaceResourceRender.d.ts +3 -0
- package/dist/compiler/containerWorkspaceResourceRender.js +128 -0
- package/dist/compiler/executionDefaults.js +0 -3
- package/dist/compiler/helpers.d.ts +1 -1
- package/dist/compiler/index.d.ts +1 -0
- package/dist/compiler/index.js +1 -0
- package/dist/compiler/moltnetArtifacts.d.ts +11 -5
- package/dist/compiler/moltnetArtifacts.js +133 -117
- package/dist/compiler/moltnetClientConfig.js +8 -2
- package/dist/compiler/moltnetConfigLowering.d.ts +36 -0
- package/dist/compiler/moltnetConfigLowering.js +125 -0
- package/dist/compiler/moltnetRuntimeConfig.d.ts +2 -0
- package/dist/compiler/moltnetRuntimeConfig.js +69 -0
- package/dist/compiler/runProject.d.ts +2 -0
- package/dist/compiler/runProject.js +20 -6
- package/dist/compiler/surfaces.d.ts +3 -13
- package/dist/compiler/surfaces.js +1 -6
- package/dist/compiler/syncProjectAuth.js +67 -19
- package/dist/compiler/types.d.ts +16 -1
- package/dist/compiler/upProject.d.ts +19 -0
- package/dist/compiler/upProject.js +37 -0
- package/dist/compiler/view/buildOrganizationView.js +22 -2
- package/dist/compiler/view/renderNetworks.js +14 -3
- package/dist/compiler/view/renderTree.js +8 -2
- package/dist/compiler/view/types.d.ts +18 -3
- package/dist/compiler/workspaceResources.d.ts +34 -0
- package/dist/compiler/workspaceResources.js +120 -0
- package/dist/manifest/executionSchemas.d.ts +106 -0
- package/dist/manifest/executionSchemas.js +140 -0
- package/dist/manifest/loadManifest.js +15 -27
- package/dist/manifest/renderSpawnfile.js +44 -52
- package/dist/manifest/renderSpawnfileNetworks.d.ts +2 -0
- package/dist/manifest/renderSpawnfileNetworks.js +63 -0
- package/dist/manifest/renderSpawnfileWorkspace.d.ts +2 -0
- package/dist/manifest/renderSpawnfileWorkspace.js +47 -0
- package/dist/manifest/scaffold.js +12 -6
- package/dist/manifest/scheduleSchemas.d.ts +15 -0
- package/dist/manifest/scheduleSchemas.js +26 -0
- package/dist/manifest/schemas.d.ts +626 -368
- package/dist/manifest/schemas.js +51 -191
- package/dist/manifest/teamNetworkSchemas.d.ts +228 -0
- package/dist/manifest/teamNetworkSchemas.js +295 -0
- package/dist/manifest/workspaceSchemas.d.ts +96 -0
- package/dist/manifest/workspaceSchemas.js +166 -0
- package/dist/report/types.d.ts +10 -0
- package/dist/runtime/common.d.ts +2 -1
- package/dist/runtime/common.js +3 -3
- package/dist/runtime/picoclaw/adapter.js +7 -0
- package/dist/runtime/picoclaw/runAuth.js +5 -41
- package/dist/runtime/tinyclaw/adapter.js +9 -2
- package/dist/runtime/tinyclaw/schedules.d.ts +9 -0
- package/dist/runtime/tinyclaw/schedules.js +62 -0
- package/dist/runtime/types.d.ts +1 -0
- package/package.json +2 -1
|
@@ -1,21 +1,11 @@
|
|
|
1
|
-
import { DocsBlock, McpServer, Secret,
|
|
1
|
+
import { DocsBlock, McpServer, Secret, SkillReference } from "../manifest/index.js";
|
|
2
2
|
import { StringMap } from "../shared/index.js";
|
|
3
|
-
import { ResolvedDocument, ResolvedSkill } from "./types.js";
|
|
3
|
+
import { ResolvedDocument, ResolvedPackage, ResolvedSkill } from "./types.js";
|
|
4
4
|
export declare const mergeSkills: (sharedSkills?: SkillReference[], localSkills?: SkillReference[]) => SkillReference[];
|
|
5
5
|
export declare const mergeResolvedSkills: (sharedSkills?: ResolvedSkill[], localSkills?: ResolvedSkill[]) => ResolvedSkill[];
|
|
6
|
+
export declare const mergePackages: (sharedPackages?: ResolvedPackage[], localPackages?: ResolvedPackage[]) => ResolvedPackage[];
|
|
6
7
|
export declare const mergeMcpServers: (sharedServers?: McpServer[], localServers?: McpServer[]) => McpServer[];
|
|
7
8
|
export declare const mergeSecrets: (sharedSecrets?: Secret[], localSecrets?: Secret[]) => Secret[];
|
|
8
9
|
export declare const mergeEnv: (sharedEnv?: StringMap, localEnv?: StringMap) => StringMap;
|
|
9
10
|
export declare const loadResolvedDocuments: (manifestPath: string, docs: DocsBlock | undefined) => Promise<ResolvedDocument[]>;
|
|
10
11
|
export declare const loadResolvedSkills: (manifestPath: string, skills?: SkillReference[]) => Promise<ResolvedSkill[]>;
|
|
11
|
-
export declare const mergeSharedSurface: (shared: SharedSurface | undefined, local: {
|
|
12
|
-
env: StringMap | undefined;
|
|
13
|
-
mcpServers: McpServer[] | undefined;
|
|
14
|
-
secrets: Secret[] | undefined;
|
|
15
|
-
skills: SkillReference[] | undefined;
|
|
16
|
-
}) => {
|
|
17
|
-
env: StringMap;
|
|
18
|
-
mcpServers: McpServer[];
|
|
19
|
-
secrets: Secret[];
|
|
20
|
-
skills: SkillReference[];
|
|
21
|
-
};
|
|
@@ -12,6 +12,7 @@ const mergeByKey = (sharedValues, localValues, getKey) => {
|
|
|
12
12
|
};
|
|
13
13
|
export const mergeSkills = (sharedSkills = [], localSkills = []) => mergeByKey(sharedSkills, localSkills, (skill) => skill.ref);
|
|
14
14
|
export const mergeResolvedSkills = (sharedSkills = [], localSkills = []) => mergeByKey(sharedSkills, localSkills, (skill) => skill.ref);
|
|
15
|
+
export const mergePackages = (sharedPackages = [], localPackages = []) => mergeByKey(sharedPackages, localPackages, (pkg) => `${pkg.manager}::${pkg.name}`);
|
|
15
16
|
export const mergeMcpServers = (sharedServers = [], localServers = []) => mergeByKey(sharedServers, localServers, (server) => server.name);
|
|
16
17
|
export const mergeSecrets = (sharedSecrets = [], localSecrets = []) => mergeByKey(sharedSecrets, localSecrets, (secret) => secret.name);
|
|
17
18
|
export const mergeEnv = (sharedEnv = {}, localEnv = {}) => ({
|
|
@@ -51,9 +52,3 @@ export const loadResolvedSkills = async (manifestPath, skills = []) => Promise.a
|
|
|
51
52
|
sourcePath
|
|
52
53
|
};
|
|
53
54
|
}));
|
|
54
|
-
export const mergeSharedSurface = (shared, local) => ({
|
|
55
|
-
env: mergeEnv(shared?.env, local.env),
|
|
56
|
-
mcpServers: mergeMcpServers(shared?.mcp_servers, local.mcpServers),
|
|
57
|
-
secrets: mergeSecrets(shared?.secrets, local.secrets),
|
|
58
|
-
skills: mergeSkills(shared?.skills, local.skills)
|
|
59
|
-
});
|
|
@@ -3,43 +3,82 @@ import { readUtf8File } from "../filesystem/index.js";
|
|
|
3
3
|
import { SpawnfileError } from "../shared/index.js";
|
|
4
4
|
import { listAgentSurfaceSecretNames } from "./agentSurfaces.js";
|
|
5
5
|
import { buildCompilePlan } from "./buildCompilePlan.js";
|
|
6
|
+
import { listMoltnetNetworkSecretNames } from "./compilePlanHelpers.js";
|
|
6
7
|
import { listExecutionModelSecretNames, resolveExecutionModelAuthMethods } from "./modelEnv.js";
|
|
7
8
|
const resolveAuthRequirements = async (inputPath) => {
|
|
8
9
|
const plan = await buildCompilePlan(inputPath);
|
|
9
10
|
const methods = new Set();
|
|
10
|
-
const
|
|
11
|
+
const optionalEnvNames = new Set();
|
|
12
|
+
const requiredEnvNames = new Set();
|
|
13
|
+
const addProjectSecret = (secret) => {
|
|
14
|
+
if (secret.required) {
|
|
15
|
+
requiredEnvNames.add(secret.name);
|
|
16
|
+
optionalEnvNames.delete(secret.name);
|
|
17
|
+
}
|
|
18
|
+
else if (!requiredEnvNames.has(secret.name)) {
|
|
19
|
+
optionalEnvNames.add(secret.name);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
11
22
|
for (const node of plan.nodes) {
|
|
12
23
|
if (node.value.kind !== "agent") {
|
|
24
|
+
for (const secret of node.value.shared.secrets) {
|
|
25
|
+
addProjectSecret(secret);
|
|
26
|
+
}
|
|
27
|
+
for (const server of node.value.shared.mcpServers) {
|
|
28
|
+
if (server.auth?.secret) {
|
|
29
|
+
addProjectSecret({ name: server.auth.secret, required: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const secretName of listMoltnetNetworkSecretNames([node])) {
|
|
33
|
+
addProjectSecret({ name: secretName, required: true });
|
|
34
|
+
}
|
|
13
35
|
continue;
|
|
14
36
|
}
|
|
37
|
+
for (const secret of node.value.secrets) {
|
|
38
|
+
addProjectSecret(secret);
|
|
39
|
+
}
|
|
40
|
+
for (const server of node.value.mcpServers) {
|
|
41
|
+
if (server.auth?.secret) {
|
|
42
|
+
addProjectSecret({ name: server.auth.secret, required: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
15
45
|
for (const method of Object.values(resolveExecutionModelAuthMethods(node.value.execution))) {
|
|
16
46
|
methods.add(method);
|
|
17
47
|
}
|
|
18
48
|
for (const envName of listExecutionModelSecretNames(node.value.execution)) {
|
|
19
|
-
|
|
49
|
+
requiredEnvNames.add(envName);
|
|
50
|
+
optionalEnvNames.delete(envName);
|
|
20
51
|
}
|
|
21
52
|
for (const envName of listAgentSurfaceSecretNames(node.value.surfaces)) {
|
|
22
|
-
|
|
53
|
+
requiredEnvNames.add(envName);
|
|
54
|
+
optionalEnvNames.delete(envName);
|
|
23
55
|
}
|
|
24
56
|
}
|
|
25
|
-
return {
|
|
57
|
+
return { methods, optionalEnvNames, requiredEnvNames };
|
|
58
|
+
};
|
|
59
|
+
const readEnvFile = async (envFilePath) => envFilePath ? parseEnvFile(await readUtf8File(envFilePath)) : {};
|
|
60
|
+
const resolveEnvValue = (envName, fileEnv) => {
|
|
61
|
+
const processValue = process.env[envName];
|
|
62
|
+
if (typeof processValue === "string" && processValue.length > 0) {
|
|
63
|
+
return processValue;
|
|
64
|
+
}
|
|
65
|
+
const fileValue = fileEnv[envName];
|
|
66
|
+
if (typeof fileValue === "string" && fileValue.length > 0) {
|
|
67
|
+
return fileValue;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
26
70
|
};
|
|
27
|
-
const resolveRequiredEnv = async (
|
|
28
|
-
if (
|
|
71
|
+
const resolveRequiredEnv = async (requiredEnvNames, optionalEnvNames, envFilePath) => {
|
|
72
|
+
if (requiredEnvNames.size === 0 && optionalEnvNames.size === 0) {
|
|
29
73
|
return {};
|
|
30
74
|
}
|
|
31
|
-
const fileEnv =
|
|
75
|
+
const fileEnv = await readEnvFile(envFilePath);
|
|
32
76
|
const resolvedEnv = {};
|
|
33
77
|
const missingEnv = [];
|
|
34
|
-
for (const envName of [...
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
resolvedEnv[envName] =
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
const fileValue = fileEnv[envName];
|
|
41
|
-
if (typeof fileValue === "string" && fileValue.length > 0) {
|
|
42
|
-
resolvedEnv[envName] = fileValue;
|
|
78
|
+
for (const envName of [...requiredEnvNames].sort()) {
|
|
79
|
+
const value = resolveEnvValue(envName, fileEnv);
|
|
80
|
+
if (value !== null) {
|
|
81
|
+
resolvedEnv[envName] = value;
|
|
43
82
|
continue;
|
|
44
83
|
}
|
|
45
84
|
missingEnv.push(envName);
|
|
@@ -47,10 +86,19 @@ const resolveRequiredEnv = async (envNames, envFilePath) => {
|
|
|
47
86
|
if (missingEnv.length > 0) {
|
|
48
87
|
throw new SpawnfileError("validation_error", `Missing required auth env: ${missingEnv.join(", ")}`);
|
|
49
88
|
}
|
|
89
|
+
for (const envName of [...optionalEnvNames].sort()) {
|
|
90
|
+
if (requiredEnvNames.has(envName)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const value = resolveEnvValue(envName, fileEnv);
|
|
94
|
+
if (value !== null) {
|
|
95
|
+
resolvedEnv[envName] = value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
50
98
|
return resolvedEnv;
|
|
51
99
|
};
|
|
52
100
|
export const syncProjectAuth = async (inputPath, options) => {
|
|
53
|
-
const {
|
|
101
|
+
const { methods, optionalEnvNames, requiredEnvNames } = await resolveAuthRequirements(inputPath);
|
|
54
102
|
await ensureAuthProfile(options.profileName);
|
|
55
103
|
if (methods.has("codex")) {
|
|
56
104
|
await importCodexAuth(options.profileName, options.codexDirectory);
|
|
@@ -58,8 +106,8 @@ export const syncProjectAuth = async (inputPath, options) => {
|
|
|
58
106
|
if (methods.has("claude-code")) {
|
|
59
107
|
await importClaudeCodeAuth(options.profileName, options.claudeCodeDirectory);
|
|
60
108
|
}
|
|
61
|
-
if (
|
|
62
|
-
await setAuthProfileEnv(options.profileName, await resolveRequiredEnv(
|
|
109
|
+
if (requiredEnvNames.size > 0 || optionalEnvNames.size > 0) {
|
|
110
|
+
await setAuthProfileEnv(options.profileName, await resolveRequiredEnv(requiredEnvNames, optionalEnvNames, options.envFilePath));
|
|
63
111
|
}
|
|
64
112
|
return requireAuthProfile(options.profileName);
|
|
65
113
|
};
|
package/dist/compiler/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { ExecutionBlock, McpServer, ModelEndpoint, Secret } from "../manifest/index.js";
|
|
1
|
+
import { AgentSchedule, ExecutionBlock, McpServer, ModelEndpoint, Secret, TeamNetworkServer } from "../manifest/index.js";
|
|
2
2
|
import { ModelAuthMethod, StringMap } from "../shared/index.js";
|
|
3
|
+
import type { ResolvedWorkspaceResource } from "./workspaceResources.js";
|
|
3
4
|
export interface ResolvedDocument {
|
|
4
5
|
content: string;
|
|
5
6
|
role: string;
|
|
@@ -12,6 +13,13 @@ export interface ResolvedSkill {
|
|
|
12
13
|
requiresMcp: string[];
|
|
13
14
|
sourcePath: string;
|
|
14
15
|
}
|
|
16
|
+
export interface ResolvedPackage {
|
|
17
|
+
id: string;
|
|
18
|
+
manager: string;
|
|
19
|
+
name: string;
|
|
20
|
+
scope?: string;
|
|
21
|
+
version?: string;
|
|
22
|
+
}
|
|
15
23
|
export interface ResolvedRuntime {
|
|
16
24
|
name: string;
|
|
17
25
|
options: Record<string, unknown>;
|
|
@@ -104,6 +112,7 @@ export interface ResolvedAgentSurfaces {
|
|
|
104
112
|
export interface ResolvedTeamNetworkRoom {
|
|
105
113
|
id: string;
|
|
106
114
|
members: string[];
|
|
115
|
+
name?: string;
|
|
107
116
|
}
|
|
108
117
|
export interface ResolvedTeamNetwork {
|
|
109
118
|
expose?: boolean;
|
|
@@ -111,6 +120,7 @@ export interface ResolvedTeamNetwork {
|
|
|
111
120
|
name: string;
|
|
112
121
|
provider: "moltnet";
|
|
113
122
|
rooms: ResolvedTeamNetworkRoom[];
|
|
123
|
+
server?: TeamNetworkServer;
|
|
114
124
|
}
|
|
115
125
|
export interface EffectiveModelTarget {
|
|
116
126
|
auth: {
|
|
@@ -140,14 +150,17 @@ export interface ResolvedAgentNode {
|
|
|
140
150
|
kind: "agent";
|
|
141
151
|
mcpServers: McpServer[];
|
|
142
152
|
name: string;
|
|
153
|
+
packages?: ResolvedPackage[];
|
|
143
154
|
policyMode: string | null;
|
|
144
155
|
policyOnDegrade: string | null;
|
|
145
156
|
runtime: ResolvedRuntime;
|
|
157
|
+
schedule?: AgentSchedule;
|
|
146
158
|
secrets: Secret[];
|
|
147
159
|
skills: ResolvedSkill[];
|
|
148
160
|
source: string;
|
|
149
161
|
surfaces?: ResolvedAgentSurfaces;
|
|
150
162
|
subagents: ResolvedSubagentRef[];
|
|
163
|
+
workspaceResources?: ResolvedWorkspaceResource[];
|
|
151
164
|
}
|
|
152
165
|
export interface ResolvedTeamNode {
|
|
153
166
|
description: string;
|
|
@@ -162,11 +175,13 @@ export interface ResolvedTeamNode {
|
|
|
162
175
|
networks?: ResolvedTeamNetwork[];
|
|
163
176
|
policyMode: string | null;
|
|
164
177
|
policyOnDegrade: string | null;
|
|
178
|
+
workspaceResources?: ResolvedWorkspaceResource[];
|
|
165
179
|
shared: {
|
|
166
180
|
env: StringMap;
|
|
167
181
|
mcpServers: McpServer[];
|
|
168
182
|
secrets: Secret[];
|
|
169
183
|
skills: ResolvedSkill[];
|
|
184
|
+
packages?: ResolvedPackage[];
|
|
170
185
|
};
|
|
171
186
|
source: string;
|
|
172
187
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type BuildProjectResult, type DockerBuildRunner } from "./buildProject.js";
|
|
2
|
+
import { type DockerRunRunner } from "./runProject.js";
|
|
3
|
+
import { CompileProjectOptions } from "./compileProject.js";
|
|
4
|
+
export interface UpProjectOptions extends CompileProjectOptions {
|
|
5
|
+
authProfile?: string;
|
|
6
|
+
buildRunner?: DockerBuildRunner;
|
|
7
|
+
containerName?: string;
|
|
8
|
+
detach?: boolean;
|
|
9
|
+
dockerCommand?: string;
|
|
10
|
+
envFilePath?: string;
|
|
11
|
+
imageTag?: string;
|
|
12
|
+
runRunner?: DockerRunRunner;
|
|
13
|
+
}
|
|
14
|
+
export interface UpProjectResult extends BuildProjectResult {
|
|
15
|
+
authProfileName: string | null;
|
|
16
|
+
containerName: string | null;
|
|
17
|
+
supportDirectory: string | null;
|
|
18
|
+
}
|
|
19
|
+
export declare const upProject: (inputPath: string, options?: UpProjectOptions) => Promise<UpProjectResult>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { removeDirectory } from "../filesystem/index.js";
|
|
2
|
+
import { buildProject } from "./buildProject.js";
|
|
3
|
+
import { createDockerRunInvocation, runDockerContainer } from "./runProject.js";
|
|
4
|
+
import { requireAuthProfile } from "../auth/index.js";
|
|
5
|
+
export const upProject = async (inputPath, options = {}) => {
|
|
6
|
+
const buildResult = await buildProject(inputPath, {
|
|
7
|
+
buildRunner: options.buildRunner,
|
|
8
|
+
clean: options.clean,
|
|
9
|
+
dockerCommand: options.dockerCommand,
|
|
10
|
+
imageTag: options.imageTag,
|
|
11
|
+
outputDirectory: options.outputDirectory
|
|
12
|
+
});
|
|
13
|
+
const authProfile = options.authProfile
|
|
14
|
+
? await requireAuthProfile(options.authProfile)
|
|
15
|
+
: null;
|
|
16
|
+
const invocation = await createDockerRunInvocation(buildResult, buildResult.imageTag, {
|
|
17
|
+
authProfile,
|
|
18
|
+
containerName: options.containerName,
|
|
19
|
+
detach: options.detach,
|
|
20
|
+
dockerCommand: options.dockerCommand,
|
|
21
|
+
envFilePath: options.envFilePath
|
|
22
|
+
});
|
|
23
|
+
try {
|
|
24
|
+
await (options.runRunner ?? runDockerContainer)(invocation);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
if (!invocation.detach) {
|
|
28
|
+
await removeDirectory(invocation.supportDirectory);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
...buildResult,
|
|
33
|
+
authProfileName: authProfile?.name ?? null,
|
|
34
|
+
containerName: invocation.containerName,
|
|
35
|
+
supportDirectory: invocation.supportDirectory
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -30,10 +30,15 @@ const buildTreeNetworkSummaries = (node) => {
|
|
|
30
30
|
return [];
|
|
31
31
|
}
|
|
32
32
|
return (node.value.networks ?? []).map((network) => ({
|
|
33
|
-
|
|
33
|
+
authMode: network.server?.auth.mode,
|
|
34
|
+
directMessages: network.server?.mode === "managed" ? network.server.direct_messages : undefined,
|
|
35
|
+
expose: network.server?.mode === "managed" ? network.server.human_ingress : undefined,
|
|
36
|
+
httpEnabled: network.server?.mode === "managed" ? network.server.human_ingress === true : false,
|
|
34
37
|
id: network.id,
|
|
35
38
|
name: network.name,
|
|
36
39
|
provider: network.provider,
|
|
40
|
+
serverMode: network.server?.mode,
|
|
41
|
+
url: network.server?.url,
|
|
37
42
|
rooms: network.rooms.map((room) => ({
|
|
38
43
|
declaredMembers: [...room.members],
|
|
39
44
|
id: room.id
|
|
@@ -101,10 +106,15 @@ const toNetworkMemberView = (membership) => ({
|
|
|
101
106
|
});
|
|
102
107
|
const createNetworkKey = (provider, networkId) => `${provider}::${networkId}`;
|
|
103
108
|
const buildNetworkDeclaration = (teamNode, network, roomMemberships) => ({
|
|
109
|
+
authMode: network.server?.auth.mode,
|
|
104
110
|
declaringTeamName: teamNode.name,
|
|
105
111
|
declaringTeamSource: teamNode.source,
|
|
106
|
-
|
|
112
|
+
directMessages: network.server?.mode === "managed" ? network.server.direct_messages : undefined,
|
|
113
|
+
expose: network.server?.mode === "managed" ? network.server.human_ingress : undefined,
|
|
114
|
+
httpEnabled: network.server?.mode === "managed" ? network.server.human_ingress === true : false,
|
|
107
115
|
name: network.name,
|
|
116
|
+
serverMode: network.server?.mode,
|
|
117
|
+
url: network.server?.url,
|
|
108
118
|
rooms: network.rooms.map((room) => {
|
|
109
119
|
const members = roomMemberships
|
|
110
120
|
.filter((membership) => membership.declaringTeamSource === teamNode.source
|
|
@@ -139,10 +149,15 @@ const buildNetworks = (plan) => {
|
|
|
139
149
|
declaringTeamName: declaration.declaringTeamName,
|
|
140
150
|
declaringTeamSource: declaration.declaringTeamSource,
|
|
141
151
|
declarations: [declaration],
|
|
152
|
+
authMode: declaration.authMode,
|
|
153
|
+
directMessages: declaration.directMessages,
|
|
142
154
|
expose: declaration.expose,
|
|
155
|
+
httpEnabled: declaration.httpEnabled,
|
|
143
156
|
id: network.id,
|
|
144
157
|
name: declaration.name,
|
|
145
158
|
provider: network.provider,
|
|
159
|
+
serverMode: declaration.serverMode,
|
|
160
|
+
url: declaration.url,
|
|
146
161
|
rooms: declaration.rooms
|
|
147
162
|
});
|
|
148
163
|
}
|
|
@@ -153,8 +168,13 @@ const buildNetworks = (plan) => {
|
|
|
153
168
|
if (firstDeclaration) {
|
|
154
169
|
network.declaringTeamName = firstDeclaration.declaringTeamName;
|
|
155
170
|
network.declaringTeamSource = firstDeclaration.declaringTeamSource;
|
|
171
|
+
network.authMode = firstDeclaration.authMode;
|
|
172
|
+
network.directMessages = firstDeclaration.directMessages;
|
|
156
173
|
network.expose = firstDeclaration.expose;
|
|
174
|
+
network.httpEnabled = firstDeclaration.httpEnabled;
|
|
157
175
|
network.name = firstDeclaration.name;
|
|
176
|
+
network.serverMode = firstDeclaration.serverMode;
|
|
177
|
+
network.url = firstDeclaration.url;
|
|
158
178
|
network.rooms = firstDeclaration.rooms;
|
|
159
179
|
}
|
|
160
180
|
}
|
|
@@ -40,19 +40,30 @@ const formatNetwork = (network, options) => {
|
|
|
40
40
|
};
|
|
41
41
|
const getDeclarations = (network) => network.declarations ?? [
|
|
42
42
|
{
|
|
43
|
+
authMode: network.authMode,
|
|
43
44
|
declaringTeamName: network.declaringTeamName,
|
|
44
45
|
declaringTeamSource: network.declaringTeamSource,
|
|
46
|
+
directMessages: network.directMessages,
|
|
45
47
|
expose: network.expose,
|
|
48
|
+
httpEnabled: network.httpEnabled,
|
|
46
49
|
name: network.name,
|
|
47
|
-
rooms: network.rooms
|
|
50
|
+
rooms: network.rooms,
|
|
51
|
+
serverMode: network.serverMode,
|
|
52
|
+
url: network.url
|
|
48
53
|
}
|
|
49
54
|
];
|
|
50
55
|
const formatDeclaration = (network, declaration, options, projectRoot) => {
|
|
51
|
-
const
|
|
56
|
+
const metadata = [
|
|
57
|
+
declaration.serverMode ? `server=${declaration.serverMode}` : undefined,
|
|
58
|
+
declaration.url ? `url=${declaration.url}` : undefined,
|
|
59
|
+
declaration.authMode ? `auth=${declaration.authMode}` : undefined,
|
|
60
|
+
declaration.directMessages === false ? "dms=disabled" : undefined,
|
|
61
|
+
declaration.expose === true || declaration.httpEnabled ? "human_ingress" : undefined
|
|
62
|
+
].filter((entry) => entry !== undefined);
|
|
52
63
|
const source = options.paths
|
|
53
64
|
? formatSourceMeta("declared_source", declaration.declaringTeamSource, projectRoot)
|
|
54
65
|
: "";
|
|
55
|
-
return `${network.id} "${declaration.name}" on ${declaration.declaringTeamName}${
|
|
66
|
+
return `${network.id} "${declaration.name}" on ${declaration.declaringTeamName}${metadata.length > 0 ? ` ${metadata.join(" ")}` : ""}${source}`;
|
|
56
67
|
};
|
|
57
68
|
export const renderOrganizationNetworks = (view, options = {}) => {
|
|
58
69
|
if (view.networks.length === 0) {
|
|
@@ -24,11 +24,17 @@ const formatEdgeLabel = (edge) => edge.relation === "subagent"
|
|
|
24
24
|
: edge.label;
|
|
25
25
|
const formatNetworkSummary = (network, options) => {
|
|
26
26
|
const id = color(network.id, "36", options);
|
|
27
|
-
const
|
|
27
|
+
const metadata = [
|
|
28
|
+
network.serverMode ? `server=${network.serverMode}` : undefined,
|
|
29
|
+
network.url ? `url=${network.url}` : undefined,
|
|
30
|
+
network.authMode ? `auth=${network.authMode}` : undefined,
|
|
31
|
+
network.directMessages === false ? "dms=disabled" : undefined,
|
|
32
|
+
network.expose === true || network.httpEnabled ? "human_ingress" : undefined
|
|
33
|
+
].filter((entry) => entry !== undefined);
|
|
28
34
|
const rooms = network.rooms
|
|
29
35
|
.map((room) => `${room.id} [${room.declaredMembers.join(", ")}]`)
|
|
30
36
|
.join("; ");
|
|
31
|
-
return `network ${id} "${network.name}"${
|
|
37
|
+
return `network ${id} "${network.name}"${metadata.length > 0 ? ` ${metadata.join(" ")}` : ""}: ${rooms}`;
|
|
32
38
|
};
|
|
33
39
|
const renderChildren = (node, options, projectRoot, prefix = "") => {
|
|
34
40
|
const glyphs = options.ascii
|
|
@@ -17,10 +17,15 @@ export interface OrganizationTreeNetworkRoomSummary {
|
|
|
17
17
|
id: string;
|
|
18
18
|
}
|
|
19
19
|
export interface OrganizationTreeNetworkSummary {
|
|
20
|
-
|
|
20
|
+
authMode?: "bearer" | "none" | "open";
|
|
21
|
+
directMessages?: boolean;
|
|
22
|
+
expose?: boolean;
|
|
23
|
+
httpEnabled?: boolean;
|
|
21
24
|
id: string;
|
|
22
25
|
name: string;
|
|
23
26
|
provider: "moltnet";
|
|
27
|
+
serverMode?: "external" | "managed";
|
|
28
|
+
url?: string;
|
|
24
29
|
rooms: OrganizationTreeNetworkRoomSummary[];
|
|
25
30
|
}
|
|
26
31
|
export interface OrganizationViewTreeEdge {
|
|
@@ -42,11 +47,16 @@ export interface OrganizationNetworkMemberView {
|
|
|
42
47
|
representativePath?: string[];
|
|
43
48
|
}
|
|
44
49
|
export interface OrganizationNetworkDeclarationView {
|
|
50
|
+
authMode?: "bearer" | "none" | "open";
|
|
45
51
|
declaringTeamName: string;
|
|
46
52
|
declaringTeamSource: string;
|
|
47
|
-
|
|
53
|
+
directMessages?: boolean;
|
|
54
|
+
expose?: boolean;
|
|
55
|
+
httpEnabled?: boolean;
|
|
48
56
|
name: string;
|
|
49
57
|
rooms: OrganizationNetworkRoomView[];
|
|
58
|
+
serverMode?: "external" | "managed";
|
|
59
|
+
url?: string;
|
|
50
60
|
}
|
|
51
61
|
export interface OrganizationNetworkRoomView {
|
|
52
62
|
declaredMembers: string[];
|
|
@@ -54,13 +64,18 @@ export interface OrganizationNetworkRoomView {
|
|
|
54
64
|
members: OrganizationNetworkMemberView[];
|
|
55
65
|
}
|
|
56
66
|
export interface OrganizationNetworkView {
|
|
67
|
+
authMode?: "bearer" | "none" | "open";
|
|
57
68
|
declaringTeamName: string;
|
|
58
69
|
declaringTeamSource: string;
|
|
59
|
-
|
|
70
|
+
directMessages?: boolean;
|
|
71
|
+
expose?: boolean;
|
|
72
|
+
httpEnabled?: boolean;
|
|
60
73
|
id: string;
|
|
61
74
|
name: string;
|
|
62
75
|
provider: "moltnet";
|
|
63
76
|
rooms: OrganizationNetworkRoomView[];
|
|
77
|
+
serverMode?: "external" | "managed";
|
|
78
|
+
url?: string;
|
|
64
79
|
declarations?: OrganizationNetworkDeclarationView[];
|
|
65
80
|
}
|
|
66
81
|
export interface OrganizationView {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TeamWorkspaceResource } from "../manifest/index.js";
|
|
2
|
+
export type WorkspaceResourceSharing = "per_agent" | "team";
|
|
3
|
+
export interface WorkspaceResourceScope {
|
|
4
|
+
kind: "agent" | "team";
|
|
5
|
+
key: string;
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
export type ResolvedWorkspaceResource = TeamWorkspaceResource & {
|
|
9
|
+
scope: WorkspaceResourceScope;
|
|
10
|
+
sharing: WorkspaceResourceSharing;
|
|
11
|
+
};
|
|
12
|
+
export interface WorkspaceResourcePlan {
|
|
13
|
+
backingPath: string;
|
|
14
|
+
branch?: string;
|
|
15
|
+
id: string;
|
|
16
|
+
kind: "git" | "volume";
|
|
17
|
+
linkPath: string;
|
|
18
|
+
mode: "mutable" | "readonly";
|
|
19
|
+
mount: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
ref?: string;
|
|
22
|
+
sharing: WorkspaceResourceSharing;
|
|
23
|
+
tag?: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const mergeWorkspaceResources: (inherited: ResolvedWorkspaceResource[] | undefined, local: TeamWorkspaceResource[] | undefined, ownerName: string, ownerScope: WorkspaceResourceScope) => ResolvedWorkspaceResource[];
|
|
27
|
+
export declare const toWorkspaceResourcePlan: (resource: ResolvedWorkspaceResource, context: {
|
|
28
|
+
targetId: string;
|
|
29
|
+
workspacePath: string;
|
|
30
|
+
}) => WorkspaceResourcePlan;
|
|
31
|
+
export declare const mergeWorkspaceResourcePlans: (resources: ResolvedWorkspaceResource[], ownerName: string, context: {
|
|
32
|
+
targetId: string;
|
|
33
|
+
workspacePath: string;
|
|
34
|
+
}) => WorkspaceResourcePlan[];
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
3
|
+
import { createShortHash, slugify } from "./helpers.js";
|
|
4
|
+
const normalizeMount = (value) => {
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
const workspaceRelative = trimmed.startsWith("${workspace}/")
|
|
7
|
+
? `./${trimmed.slice("${workspace}/".length)}`
|
|
8
|
+
: trimmed;
|
|
9
|
+
const collapsed = workspaceRelative.replace(/\/+/g, "/");
|
|
10
|
+
if (collapsed.startsWith("./")) {
|
|
11
|
+
const relativePath = collapsed.slice(2).replace(/\/+$/u, "");
|
|
12
|
+
return `./${relativePath}`;
|
|
13
|
+
}
|
|
14
|
+
return collapsed.length > 1 ? collapsed.replace(/\/+$/u, "") : "/";
|
|
15
|
+
};
|
|
16
|
+
const normalizeResourceIdentity = (resource) => {
|
|
17
|
+
if (resource.kind === "git") {
|
|
18
|
+
return JSON.stringify({
|
|
19
|
+
branch: resource.branch?.trim() ?? "",
|
|
20
|
+
kind: "git",
|
|
21
|
+
mode: resource.mode,
|
|
22
|
+
mount: normalizeMount(resource.mount),
|
|
23
|
+
ref: resource.ref?.trim() ?? "",
|
|
24
|
+
sharing: resource.sharing,
|
|
25
|
+
tag: resource.tag?.trim() ?? "",
|
|
26
|
+
url: resource.url.trim()
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return JSON.stringify({
|
|
30
|
+
kind: "volume",
|
|
31
|
+
mode: resource.mode,
|
|
32
|
+
mount: normalizeMount(resource.mount),
|
|
33
|
+
name: resource.name?.trim() ?? "",
|
|
34
|
+
scope: resource.sharing === "team" ? resource.scope.key : "",
|
|
35
|
+
sharing: resource.sharing
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
const mountsOverlap = (left, right) => left === right ||
|
|
39
|
+
left.startsWith(`${right}/`) ||
|
|
40
|
+
right.startsWith(`${left}/`);
|
|
41
|
+
const resolveSharing = (resource) => resource.sharing ?? "per_agent";
|
|
42
|
+
const toResolvedResource = (resource, scope) => ({
|
|
43
|
+
...resource,
|
|
44
|
+
mount: normalizeMount(resource.mount),
|
|
45
|
+
scope,
|
|
46
|
+
sharing: resolveSharing(resource)
|
|
47
|
+
});
|
|
48
|
+
const createPathSegment = (value) => {
|
|
49
|
+
const slug = slugify(value);
|
|
50
|
+
const hash = createShortHash(value);
|
|
51
|
+
return slug ? `${slug}-${hash}` : hash;
|
|
52
|
+
};
|
|
53
|
+
const createScopeSegment = (scope) => `${scope.kind}-${createPathSegment(`${scope.name}:${scope.key}`)}`;
|
|
54
|
+
const createResourceSegment = (resource) => createPathSegment(resource.kind === "volume" && resource.name ? resource.name : resource.id);
|
|
55
|
+
const resolveLinkPath = (mount, workspacePath) => mount.startsWith("./")
|
|
56
|
+
? path.posix.join(workspacePath, mount.slice(2))
|
|
57
|
+
: mount;
|
|
58
|
+
const resolveBackingPath = (resource, targetId) => {
|
|
59
|
+
const resourceSegment = createResourceSegment(resource);
|
|
60
|
+
if (resource.sharing === "team") {
|
|
61
|
+
return path.posix.join("/var/lib/spawnfile/resources/teams", createScopeSegment(resource.scope), resourceSegment);
|
|
62
|
+
}
|
|
63
|
+
return path.posix.join("/var/lib/spawnfile/resources/instances", createPathSegment(targetId), resourceSegment);
|
|
64
|
+
};
|
|
65
|
+
export const mergeWorkspaceResources = (inherited = [], local = [], ownerName, ownerScope) => {
|
|
66
|
+
const merged = [];
|
|
67
|
+
const identityById = new Map();
|
|
68
|
+
const resources = [
|
|
69
|
+
...inherited,
|
|
70
|
+
...local.map((resource) => toResolvedResource(resource, ownerScope))
|
|
71
|
+
];
|
|
72
|
+
for (const resource of resources) {
|
|
73
|
+
const mount = normalizeMount(resource.mount);
|
|
74
|
+
const identity = normalizeResourceIdentity(resource);
|
|
75
|
+
const existingIdentity = identityById.get(resource.id);
|
|
76
|
+
if (existingIdentity) {
|
|
77
|
+
if (existingIdentity !== identity) {
|
|
78
|
+
throw new SpawnfileError("validation_error", `Workspace resource ${resource.id} resolves differently for ${ownerName}`);
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const overlapping = merged.find((candidate) => candidate.id !== resource.id &&
|
|
83
|
+
mountsOverlap(normalizeMount(candidate.mount), mount));
|
|
84
|
+
if (overlapping) {
|
|
85
|
+
throw new SpawnfileError("validation_error", `Workspace resources ${overlapping.id} and ${resource.id} use overlapping mounts for ${ownerName}`);
|
|
86
|
+
}
|
|
87
|
+
identityById.set(resource.id, identity);
|
|
88
|
+
merged.push({ ...resource, mount });
|
|
89
|
+
}
|
|
90
|
+
return merged.sort((left, right) => left.id.localeCompare(right.id));
|
|
91
|
+
};
|
|
92
|
+
export const toWorkspaceResourcePlan = (resource, context) => resource.kind === "git"
|
|
93
|
+
? {
|
|
94
|
+
backingPath: resolveBackingPath(resource, context.targetId),
|
|
95
|
+
...(resource.branch ? { branch: resource.branch } : {}),
|
|
96
|
+
id: resource.id,
|
|
97
|
+
kind: "git",
|
|
98
|
+
linkPath: resolveLinkPath(normalizeMount(resource.mount), context.workspacePath),
|
|
99
|
+
mode: resource.mode,
|
|
100
|
+
mount: normalizeMount(resource.mount),
|
|
101
|
+
...(resource.ref ? { ref: resource.ref } : {}),
|
|
102
|
+
sharing: resource.sharing,
|
|
103
|
+
...(resource.tag ? { tag: resource.tag } : {}),
|
|
104
|
+
url: resource.url
|
|
105
|
+
}
|
|
106
|
+
: {
|
|
107
|
+
backingPath: resolveBackingPath(resource, context.targetId),
|
|
108
|
+
id: resource.id,
|
|
109
|
+
kind: "volume",
|
|
110
|
+
linkPath: resolveLinkPath(normalizeMount(resource.mount), context.workspacePath),
|
|
111
|
+
mode: resource.mode,
|
|
112
|
+
mount: normalizeMount(resource.mount),
|
|
113
|
+
...(resource.name ? { name: resource.name } : {}),
|
|
114
|
+
sharing: resource.sharing
|
|
115
|
+
};
|
|
116
|
+
export const mergeWorkspaceResourcePlans = (resources, ownerName, context) => mergeWorkspaceResources(resources, [], ownerName, {
|
|
117
|
+
kind: "agent",
|
|
118
|
+
key: context.targetId,
|
|
119
|
+
name: context.targetId
|
|
120
|
+
}).map((resource) => toWorkspaceResourcePlan(resource, context));
|