spawnfile 0.1.10 → 0.1.11

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.
@@ -22,7 +22,9 @@ export const resolveTeamNetworks = (manifest) => (manifest.networks ?? []).map((
22
22
  rooms: network.rooms.map((room) => ({
23
23
  id: room.id,
24
24
  members: [...room.members],
25
- ...(room.name ? { name: room.name } : {})
25
+ ...(room.name ? { name: room.name } : {}),
26
+ ...(room.visibility ? { visibility: room.visibility } : {}),
27
+ ...(room.write_policy ? { write_policy: room.write_policy } : {})
26
28
  }))
27
29
  };
28
30
  if (network.server) {
@@ -10,6 +10,7 @@ export interface CompiledNodeOutput {
10
10
  report: {
11
11
  output_dir: string | null;
12
12
  };
13
+ slug?: string;
13
14
  value: ResolvedAgentNode | ResolvedTeamNode;
14
15
  }
15
16
  export declare const writeEmittedFiles: (outputDirectory: string, files: EmittedFile[]) => Promise<void>;
@@ -90,7 +90,7 @@ export const injectMoltnetWorkspaceFiles = async (outputDirectory, compiledNodes
90
90
  continue;
91
91
  }
92
92
  const runtimeOutputDirectory = path.join(outputDirectory, compiled.report.output_dir);
93
- const moltnetClientConfigFiles = createMoltnetClientConfigFiles(compiled.value, artifacts);
93
+ const moltnetClientConfigFiles = createMoltnetClientConfigFiles(compiled.value, artifacts, compiled.slug);
94
94
  if (moltnetClientConfigFiles.length === 0) {
95
95
  continue;
96
96
  }
@@ -14,6 +14,8 @@ export interface MoltnetServerPlan {
14
14
  id: string;
15
15
  members: string[];
16
16
  name?: string;
17
+ visibility?: "public" | "private";
18
+ write_policy?: "members" | "operators" | "registered_agents";
17
19
  }>;
18
20
  server: TeamNetworkServer;
19
21
  secretPatches: MoltnetSecretPatch[];
@@ -1,7 +1,8 @@
1
1
  import { SpawnfileError } from "../shared/index.js";
2
- import { createMoltnetOpenTokenDirectory, createMoltnetNativeServerConfig, createMoltnetNodeConfigPath, createMoltnetServerConfigPath, resolveMoltnetBaseUrl, resolveMoltnetClientAuth, resolveMoltnetStorePersistenceMountPath } from "./moltnetConfigLowering.js";
2
+ import { createMoltnetOpenTokenDirectory, createMoltnetNativeServerConfig, createMoltnetNodeConfigPath, createMoltnetServerConfigPath, resolveMoltnetBaseUrl, resolveMoltnetStorePersistenceMountPath } from "./moltnetConfigLowering.js";
3
3
  import { listConcreteMoltnetRoomMemberIds } from "./moltnetRoomMemberships.js";
4
- import { resolveRuntimeConfig } from "./moltnetRuntimeConfig.js";
4
+ import { assertCompatibleMoltnetNetworkName, assertCompatibleMoltnetRoomPolicy, assertCompatibleMoltnetServer } from "./moltnetRoomPolicyCompatibility.js";
5
+ import { createMoltnetNodeConfigContent } from "./moltnetNodeConfig.js";
5
6
  import { createShortHash, slugify } from "./helpers.js";
6
7
  const DEFAULT_MOLTNET_PORT = 8787;
7
8
  const ROOTFS_PREFIX = "container/rootfs";
@@ -39,6 +40,8 @@ export const generateMoltnetArtifacts = async (plan) => {
39
40
  if (existingPlan.baseUrl !== resolveMoltnetBaseUrl(server)) {
40
41
  throw new SpawnfileError("validation_error", `Duplicate Moltnet network ${network.id} declares conflicting server URL`);
41
42
  }
43
+ assertCompatibleMoltnetNetworkName(network.id, existingPlan.name, network.name);
44
+ assertCompatibleMoltnetServer(network.id, existingPlan.server, server);
42
45
  for (const room of network.rooms) {
43
46
  const concreteMembers = listConcreteMoltnetRoomMemberIds(plan, teamNode.value, network.id, room);
44
47
  const existingRoom = existingPlan.rooms.find((entry) => entry.id === room.id);
@@ -46,12 +49,18 @@ export const generateMoltnetArtifacts = async (plan) => {
46
49
  existingRoom.members = [
47
50
  ...new Set([...existingRoom.members, ...concreteMembers])
48
51
  ].sort();
52
+ assertCompatibleMoltnetRoomPolicy(network.id, room.id, "visibility", existingRoom.visibility, room.visibility);
53
+ assertCompatibleMoltnetRoomPolicy(network.id, room.id, "write_policy", existingRoom.write_policy, room.write_policy);
54
+ existingRoom.visibility ??= room.visibility;
55
+ existingRoom.write_policy ??= room.write_policy;
49
56
  }
50
57
  else {
51
58
  existingPlan.rooms.push({
52
59
  id: room.id,
53
60
  members: concreteMembers,
54
- ...(room.name ? { name: room.name } : {})
61
+ ...(room.name ? { name: room.name } : {}),
62
+ ...(room.visibility ? { visibility: room.visibility } : {}),
63
+ ...(room.write_policy ? { write_policy: room.write_policy } : {})
55
64
  });
56
65
  }
57
66
  }
@@ -73,7 +82,9 @@ export const generateMoltnetArtifacts = async (plan) => {
73
82
  rooms: network.rooms.map((room) => ({
74
83
  id: room.id,
75
84
  members: listConcreteMoltnetRoomMemberIds(plan, teamNode.value, network.id, room),
76
- ...(room.name ? { name: room.name } : {})
85
+ ...(room.name ? { name: room.name } : {}),
86
+ ...(room.visibility ? { visibility: room.visibility } : {}),
87
+ ...(room.write_policy ? { write_policy: room.write_policy } : {})
77
88
  })),
78
89
  server,
79
90
  secretPatches: [],
@@ -99,38 +110,35 @@ export const generateMoltnetArtifacts = async (plan) => {
99
110
  throw new SpawnfileError("validation_error", `Moltnet persistent mount ${mount.id} resolves to conflicting targets`);
100
111
  }
101
112
  };
102
- for (const teamNode of teamNodes) {
103
- for (const network of teamNode.value.networks ?? []) {
104
- const serverPlan = serverPlans.get(createServerKey(network.id));
105
- if (!serverPlan || !network.server || network.server.mode !== "managed" || !serverPlan.configPath) {
106
- continue;
107
- }
108
- const native = createMoltnetNativeServerConfig({
109
- networkId: network.id,
110
- networkName: network.name,
111
- rooms: serverPlan.rooms,
112
- server: network.server
113
- });
114
- serverPlan.secretPatches = native.secretPatches;
115
- configFiles.push({
116
- content: `${JSON.stringify(native.config, null, 2)}\n`,
117
- mode: 0o600,
118
- path: createMoltnetServerConfigPath(serverPlan.id)
113
+ for (const serverPlan of serverPlans.values()) {
114
+ if (serverPlan.mode !== "managed" || serverPlan.server.mode !== "managed" || !serverPlan.configPath) {
115
+ continue;
116
+ }
117
+ const native = createMoltnetNativeServerConfig({
118
+ networkId: serverPlan.networkId,
119
+ networkName: serverPlan.name,
120
+ rooms: serverPlan.rooms,
121
+ server: serverPlan.server
122
+ });
123
+ serverPlan.secretPatches = native.secretPatches;
124
+ configFiles.push({
125
+ content: `${JSON.stringify(native.config, null, 2)}\n`,
126
+ mode: 0o600,
127
+ path: createMoltnetServerConfigPath(serverPlan.id)
128
+ });
129
+ const storeMountPath = resolveMoltnetStorePersistenceMountPath(serverPlan.networkId, serverPlan.server.store);
130
+ if (storeMountPath) {
131
+ const mountId = `moltnet-${serverPlan.networkId}-store`;
132
+ const store = serverPlan.server.store;
133
+ const volumeName = store.kind === "sqlite" || store.kind === "json"
134
+ ? store.persistence?.name
135
+ : undefined;
136
+ addPersistentMount({
137
+ id: mountId,
138
+ mountPath: storeMountPath,
139
+ reason: `managed Moltnet ${serverPlan.server.store.kind} store for ${serverPlan.networkId}`,
140
+ volumeName: createPersistentVolumeName(plan.root, mountId, volumeName)
119
141
  });
120
- const storeMountPath = resolveMoltnetStorePersistenceMountPath(network.id, network.server.store);
121
- if (storeMountPath) {
122
- const mountId = `moltnet-${network.id}-store`;
123
- const store = network.server.store;
124
- const volumeName = store.kind === "sqlite" || store.kind === "json"
125
- ? store.persistence?.name
126
- : undefined;
127
- addPersistentMount({
128
- id: mountId,
129
- mountPath: storeMountPath,
130
- reason: `managed Moltnet ${network.server.store.kind} store for ${network.id}`,
131
- volumeName: createPersistentVolumeName(plan.root, mountId, volumeName)
132
- });
133
- }
134
142
  }
135
143
  }
136
144
  for (const node of plan.nodes) {
@@ -169,10 +177,15 @@ export const generateMoltnetArtifacts = async (plan) => {
169
177
  throw new SpawnfileError("validation_error", `Duplicate Moltnet node attachment for ${attachment.network}/${attachment.memberId}`);
170
178
  }
171
179
  nodePlanKeys.add(nodePlanKey);
172
- const clientAuth = resolveMoltnetClientAuth(network.server, attachment.network, attachment.memberId, node.slug);
173
- const usesPerAttachmentOpenToken = clientAuth.mode === "open" &&
174
- clientAuth.staticToken !== true &&
175
- Boolean(clientAuth.tokenEnv || clientAuth.tokenPath);
180
+ const nodeConfig = createMoltnetNodeConfigContent({
181
+ agentNode,
182
+ attachment: { ...attachment, memberId: attachment.memberId },
183
+ networkServer: serverPlan.server,
184
+ nodeSlug: node.slug,
185
+ plan,
186
+ serverPlan
187
+ });
188
+ const { clientAuth, usesPerAttachmentOpenToken } = nodeConfig;
176
189
  if (usesPerAttachmentOpenToken && clientAuth.tokenPath) {
177
190
  const mountId = `agent-${node.slug}-moltnet-tokens`;
178
191
  addPersistentMount({
@@ -183,66 +196,7 @@ export const generateMoltnetArtifacts = async (plan) => {
183
196
  });
184
197
  }
185
198
  configFiles.push({
186
- content: `${JSON.stringify({
187
- version: "moltnet.node.v1",
188
- moltnet: {
189
- base_url: serverPlan.baseUrl,
190
- network_id: attachment.network,
191
- ...(clientAuth.mode === "none"
192
- ? {}
193
- : { auth_mode: clientAuth.mode }),
194
- ...(clientAuth.staticToken
195
- ? { static_token: true }
196
- : {}),
197
- ...(!usesPerAttachmentOpenToken && clientAuth.tokenEnv
198
- ? {
199
- token_env: clientAuth.tokenEnv
200
- }
201
- : {}),
202
- ...(!usesPerAttachmentOpenToken && clientAuth.tokenPath
203
- ? {
204
- token_path: clientAuth.tokenPath
205
- }
206
- : {})
207
- },
208
- attachments: [
209
- {
210
- agent: {
211
- id: attachment.memberId,
212
- name: agentNode.name
213
- },
214
- ...(usesPerAttachmentOpenToken
215
- ? {
216
- moltnet: {
217
- ...(clientAuth.tokenEnv ? { token_env: clientAuth.tokenEnv } : {}),
218
- ...(clientAuth.tokenPath ? { token_path: clientAuth.tokenPath } : {})
219
- }
220
- }
221
- : {}),
222
- runtime: resolveRuntimeConfig(plan, agentNode, node.slug, attachment.network, attachment.memberId),
223
- ...(attachment.rooms
224
- ? {
225
- rooms: Object.entries(attachment.rooms)
226
- .sort(([left], [right]) => left.localeCompare(right))
227
- .map(([roomId, policy]) => ({
228
- id: roomId,
229
- ...(policy.read ? { read: policy.read } : {}),
230
- ...(policy.reply ? { reply: policy.reply } : {})
231
- }))
232
- }
233
- : {}),
234
- ...(attachment.dms
235
- ? {
236
- dms: {
237
- enabled: attachment.dms.enabled,
238
- ...(attachment.dms.read ? { read: attachment.dms.read } : {}),
239
- ...(attachment.dms.reply ? { reply: attachment.dms.reply } : {})
240
- }
241
- }
242
- : {})
243
- }
244
- ]
245
- }, null, 2)}\n`,
199
+ content: nodeConfig.content,
246
200
  mode: 0o600,
247
201
  path: configPath
248
202
  });
@@ -8,4 +8,4 @@ export interface MoltnetWorkspaceLayout {
8
8
  workspaceRootPath: string;
9
9
  }
10
10
  export declare const resolveMoltnetWorkspaceLayout: (runtimeName: string, agentName: string) => MoltnetWorkspaceLayout;
11
- export declare const createMoltnetClientConfigFiles: (node: ResolvedAgentNode, artifacts: MoltnetArtifacts) => EmittedFile[];
11
+ export declare const createMoltnetClientConfigFiles: (node: ResolvedAgentNode, artifacts: MoltnetArtifacts, agentSlug?: string) => EmittedFile[];
@@ -19,16 +19,17 @@ const findServerPlan = (artifacts, attachment) => {
19
19
  }
20
20
  return plan;
21
21
  };
22
- const createAttachmentConfig = (node, artifacts, attachment) => {
22
+ const createAttachmentConfig = (node, artifacts, attachment, agentSlug) => {
23
23
  if (!attachment.memberId) {
24
24
  throw new SpawnfileError("compile_error", `Moltnet client config requires a resolved member id for ${node.name}`);
25
25
  }
26
26
  const serverPlan = findServerPlan(artifacts, attachment);
27
- const auth = resolveMoltnetClientAuth(serverPlan.server, attachment.network, attachment.memberId);
27
+ const auth = resolveMoltnetClientAuth(serverPlan.server, attachment.network, attachment.memberId, agentSlug);
28
28
  return {
29
29
  agent_name: node.name,
30
30
  auth: {
31
31
  mode: auth.mode,
32
+ ...(auth.registration ? { registration: auth.registration } : {}),
32
33
  ...(auth.tokenEnv ? { token_env: auth.tokenEnv } : {}),
33
34
  ...(auth.tokenPath ? { token_path: auth.tokenPath } : {})
34
35
  },
@@ -49,11 +50,16 @@ const createAttachmentConfig = (node, artifacts, attachment) => {
49
50
  ? {
50
51
  rooms: Object.entries(attachment.rooms)
51
52
  .sort(([left], [right]) => left.localeCompare(right))
52
- .map(([roomId, policy]) => ({
53
- id: roomId,
54
- ...(policy.read ? { read: policy.read } : {}),
55
- ...(policy.reply ? { reply: policy.reply } : {})
56
- }))
53
+ .map(([roomId, policy]) => {
54
+ const room = serverPlan.rooms.find((entry) => entry.id === roomId);
55
+ return {
56
+ id: roomId,
57
+ ...(room?.visibility ? { visibility: room.visibility } : {}),
58
+ ...(room?.write_policy ? { write_policy: room.write_policy } : {}),
59
+ ...(policy.read ? { read: policy.read } : {}),
60
+ ...(policy.reply ? { reply: policy.reply } : {})
61
+ };
62
+ })
57
63
  }
58
64
  : {})
59
65
  };
@@ -80,7 +86,7 @@ export const resolveMoltnetWorkspaceLayout = (runtimeName, agentName) => {
80
86
  }
81
87
  throw new SpawnfileError("compile_error", `Moltnet client config does not know how to emit files for runtime ${runtimeName}`);
82
88
  };
83
- export const createMoltnetClientConfigFiles = (node, artifacts) => {
89
+ export const createMoltnetClientConfigFiles = (node, artifacts, agentSlug) => {
84
90
  const attachments = node.surfaces?.moltnet;
85
91
  if (!attachments || attachments.length === 0) {
86
92
  return [];
@@ -88,7 +94,7 @@ export const createMoltnetClientConfigFiles = (node, artifacts) => {
88
94
  const layout = resolveMoltnetWorkspaceLayout(node.runtime.name, node.name);
89
95
  return [
90
96
  {
91
- content: createConfigContent(node, attachments.map((attachment) => createAttachmentConfig(node, artifacts, attachment))),
97
+ content: createConfigContent(node, attachments.map((attachment) => createAttachmentConfig(node, artifacts, attachment, agentSlug))),
92
98
  path: layout.clientConfigPath
93
99
  }
94
100
  ];
@@ -5,6 +5,7 @@ export interface MoltnetSecretPatch {
5
5
  }
6
6
  export interface MoltnetClientAuthPlan {
7
7
  mode: "bearer" | "none" | "open";
8
+ registration?: "disabled" | "open" | "token";
8
9
  staticToken?: boolean;
9
10
  tokenEnv?: string;
10
11
  tokenPath?: string;
@@ -13,6 +14,8 @@ export interface MoltnetNativeRoomConfig {
13
14
  id: string;
14
15
  members: string[];
15
16
  name?: string;
17
+ visibility?: "public" | "private";
18
+ write_policy?: "members" | "operators" | "registered_agents";
16
19
  }
17
20
  export interface MoltnetNativeServerConfigInput {
18
21
  networkId: string;
@@ -55,14 +55,19 @@ export const resolveMoltnetClientAuth = (server, networkId, memberId, agentSlug)
55
55
  return { mode: "none" };
56
56
  }
57
57
  const client = server.auth.client;
58
- if (server.auth.mode === "open" && !client) {
58
+ const registrationOpen = server.auth.mode === "open" || server.auth.agent_registration === "open";
59
+ if (registrationOpen && !client) {
59
60
  return {
60
61
  mode: "open",
62
+ registration: "open",
61
63
  tokenPath: createMoltnetOpenTokenPath(networkId, memberId, agentSlug)
62
64
  };
63
65
  }
64
66
  if (!client) {
65
- return { mode: server.auth.mode };
67
+ return {
68
+ mode: server.auth.mode,
69
+ ...(server.auth.agent_registration ? { registration: server.auth.agent_registration } : {})
70
+ };
66
71
  }
67
72
  const tokenEnv = client.token_env
68
73
  ?? (client.token_id && server.mode === "managed"
@@ -70,6 +75,7 @@ export const resolveMoltnetClientAuth = (server, networkId, memberId, agentSlug)
70
75
  : undefined);
71
76
  return {
72
77
  mode: server.auth.mode,
78
+ ...(server.auth.agent_registration ? { registration: server.auth.agent_registration } : {}),
73
79
  ...(client.static_token ? { staticToken: true } : {}),
74
80
  ...(tokenEnv ? { tokenEnv } : {}),
75
81
  ...(client.token_path ? { tokenPath: client.token_path } : {})
@@ -129,6 +135,7 @@ export const createMoltnetNativeServerConfig = ({ networkId, networkName, rooms,
129
135
  },
130
136
  server: {
131
137
  listen_addr: renderMoltnetListenAddr(server),
138
+ ...(server.console ? { console: server.console } : {}),
132
139
  ...(server.human_ingress !== undefined ? { human_ingress: server.human_ingress } : {}),
133
140
  ...(server.direct_messages !== undefined ? { direct_messages: server.direct_messages } : {}),
134
141
  ...(server.debug_events !== undefined ? { debug_events: server.debug_events } : {}),
@@ -139,12 +146,16 @@ export const createMoltnetNativeServerConfig = ({ networkId, networkName, rooms,
139
146
  },
140
147
  auth: {
141
148
  mode: server.auth.mode,
149
+ ...(server.auth.public_read !== undefined ? { public_read: server.auth.public_read } : {}),
150
+ ...(server.auth.agent_registration ? { agent_registration: server.auth.agent_registration } : {}),
142
151
  ...(tokens.length > 0 ? { tokens } : {})
143
152
  },
144
153
  storage: storageConfigFor(networkId, server.store),
145
154
  rooms: rooms.map((room) => ({
146
155
  id: room.id,
147
156
  ...(room.name ? { name: room.name } : {}),
157
+ ...(room.visibility ? { visibility: room.visibility } : {}),
158
+ ...(room.write_policy ? { write_policy: room.write_policy } : {}),
148
159
  members: room.members
149
160
  })),
150
161
  ...(pairings.length > 0 ? { pairings } : {})
@@ -0,0 +1,27 @@
1
+ import type { TeamNetworkServer } from "../manifest/index.js";
2
+ import { type MoltnetClientAuthPlan } from "./moltnetConfigLowering.js";
3
+ import type { CompilePlan, ResolvedAgentNode, ResolvedMoltnetAttachment } from "./types.js";
4
+ interface MoltnetNodeConfigServerPlan {
5
+ baseUrl: string;
6
+ rooms: Array<{
7
+ id: string;
8
+ visibility?: "public" | "private";
9
+ write_policy?: "members" | "operators" | "registered_agents";
10
+ }>;
11
+ }
12
+ export interface MoltnetNodeConfigResult {
13
+ clientAuth: MoltnetClientAuthPlan;
14
+ content: string;
15
+ usesPerAttachmentOpenToken: boolean;
16
+ }
17
+ export declare const createMoltnetNodeConfigContent: ({ agentNode, attachment, networkServer, nodeSlug, plan, serverPlan }: {
18
+ agentNode: ResolvedAgentNode;
19
+ attachment: ResolvedMoltnetAttachment & {
20
+ memberId: string;
21
+ };
22
+ networkServer: TeamNetworkServer;
23
+ nodeSlug: string;
24
+ plan: CompilePlan;
25
+ serverPlan: MoltnetNodeConfigServerPlan;
26
+ }) => MoltnetNodeConfigResult;
27
+ export {};
@@ -0,0 +1,70 @@
1
+ import { resolveMoltnetClientAuth } from "./moltnetConfigLowering.js";
2
+ import { resolveRuntimeConfig } from "./moltnetRuntimeConfig.js";
3
+ export const createMoltnetNodeConfigContent = ({ agentNode, attachment, networkServer, nodeSlug, plan, serverPlan }) => {
4
+ const clientAuth = resolveMoltnetClientAuth(networkServer, attachment.network, attachment.memberId, nodeSlug);
5
+ const usesPerAttachmentOpenToken = clientAuth.mode === "open" &&
6
+ clientAuth.staticToken !== true &&
7
+ Boolean(clientAuth.tokenEnv || clientAuth.tokenPath);
8
+ return {
9
+ clientAuth,
10
+ content: `${JSON.stringify({
11
+ version: "moltnet.node.v1",
12
+ moltnet: {
13
+ base_url: serverPlan.baseUrl,
14
+ network_id: attachment.network,
15
+ ...(clientAuth.mode === "none" ? {} : { auth_mode: clientAuth.mode }),
16
+ ...(clientAuth.registration ? { registration: clientAuth.registration } : {}),
17
+ ...(clientAuth.staticToken ? { static_token: true } : {}),
18
+ ...(!usesPerAttachmentOpenToken && clientAuth.tokenEnv
19
+ ? { token_env: clientAuth.tokenEnv }
20
+ : {}),
21
+ ...(!usesPerAttachmentOpenToken && clientAuth.tokenPath
22
+ ? { token_path: clientAuth.tokenPath }
23
+ : {})
24
+ },
25
+ attachments: [
26
+ {
27
+ agent: {
28
+ id: attachment.memberId,
29
+ name: agentNode.name
30
+ },
31
+ ...(usesPerAttachmentOpenToken
32
+ ? {
33
+ moltnet: {
34
+ ...(clientAuth.tokenEnv ? { token_env: clientAuth.tokenEnv } : {}),
35
+ ...(clientAuth.tokenPath ? { token_path: clientAuth.tokenPath } : {})
36
+ }
37
+ }
38
+ : {}),
39
+ runtime: resolveRuntimeConfig(plan, agentNode, nodeSlug, attachment.network, attachment.memberId),
40
+ ...(attachment.rooms
41
+ ? {
42
+ rooms: Object.entries(attachment.rooms)
43
+ .sort(([left], [right]) => left.localeCompare(right))
44
+ .map(([roomId, policy]) => {
45
+ const room = serverPlan.rooms.find((entry) => entry.id === roomId);
46
+ return {
47
+ id: roomId,
48
+ ...(room?.visibility ? { visibility: room.visibility } : {}),
49
+ ...(room?.write_policy ? { write_policy: room.write_policy } : {}),
50
+ ...(policy.read ? { read: policy.read } : {}),
51
+ ...(policy.reply ? { reply: policy.reply } : {})
52
+ };
53
+ })
54
+ }
55
+ : {}),
56
+ ...(attachment.dms
57
+ ? {
58
+ dms: {
59
+ enabled: attachment.dms.enabled,
60
+ ...(attachment.dms.read ? { read: attachment.dms.read } : {}),
61
+ ...(attachment.dms.reply ? { reply: attachment.dms.reply } : {})
62
+ }
63
+ }
64
+ : {})
65
+ }
66
+ ]
67
+ }, null, 2)}\n`,
68
+ usesPerAttachmentOpenToken
69
+ };
70
+ };
@@ -0,0 +1,4 @@
1
+ import type { TeamNetworkServer } from "../manifest/index.js";
2
+ export declare const assertCompatibleMoltnetNetworkName: (networkId: string, existing: string | undefined, next: string | undefined) => void;
3
+ export declare const assertCompatibleMoltnetServer: (networkId: string, existing: TeamNetworkServer, next: TeamNetworkServer) => void;
4
+ export declare const assertCompatibleMoltnetRoomPolicy: (networkId: string, roomId: string, field: "visibility" | "write_policy", existing: string | undefined, next: string | undefined) => void;
@@ -0,0 +1,28 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ const stableStringify = (value) => {
3
+ if (Array.isArray(value)) {
4
+ return `[${value.map(stableStringify).join(",")}]`;
5
+ }
6
+ if (value && typeof value === "object") {
7
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(",")}}`;
8
+ }
9
+ return JSON.stringify(value);
10
+ };
11
+ export const assertCompatibleMoltnetNetworkName = (networkId, existing, next) => {
12
+ if (existing !== undefined && next !== undefined && existing !== next) {
13
+ throw new SpawnfileError("validation_error", `Duplicate Moltnet network ${networkId} declares conflicting network name: ${existing} vs ${next}`);
14
+ }
15
+ };
16
+ export const assertCompatibleMoltnetServer = (networkId, existing, next) => {
17
+ if (stableStringify(existing.auth) !== stableStringify(next.auth)) {
18
+ throw new SpawnfileError("validation_error", `Duplicate Moltnet network ${networkId} declares conflicting server.auth policy`);
19
+ }
20
+ if (stableStringify(existing) !== stableStringify(next)) {
21
+ throw new SpawnfileError("validation_error", `Duplicate Moltnet network ${networkId} declares conflicting server definition`);
22
+ }
23
+ };
24
+ export const assertCompatibleMoltnetRoomPolicy = (networkId, roomId, field, existing, next) => {
25
+ if (existing !== undefined && next !== undefined && existing !== next) {
26
+ throw new SpawnfileError("validation_error", `Duplicate Moltnet network ${networkId} room ${roomId} declares conflicting ${field}: ${existing} vs ${next}`);
27
+ }
28
+ };
@@ -113,6 +113,8 @@ export interface ResolvedTeamNetworkRoom {
113
113
  id: string;
114
114
  members: string[];
115
115
  name?: string;
116
+ visibility?: "public" | "private";
117
+ write_policy?: "members" | "operators" | "registered_agents";
116
118
  }
117
119
  export interface ResolvedTeamNetwork {
118
120
  expose?: boolean;
@@ -30,7 +30,11 @@ const buildTreeNetworkSummaries = (node) => {
30
30
  return [];
31
31
  }
32
32
  return (node.value.networks ?? []).map((network) => ({
33
+ agentRegistration: network.server?.auth.agent_registration,
33
34
  authMode: network.server?.auth.mode,
35
+ consoleAnalytics: network.server?.mode === "managed"
36
+ ? network.server.console?.analytics?.provider
37
+ : undefined,
34
38
  debugEvents: network.server?.mode === "managed" ? network.server.debug_events : undefined,
35
39
  directMessages: network.server?.mode === "managed" ? network.server.direct_messages : undefined,
36
40
  expose: network.server?.mode === "managed" ? network.server.human_ingress : undefined,
@@ -38,11 +42,14 @@ const buildTreeNetworkSummaries = (node) => {
38
42
  id: network.id,
39
43
  name: network.name,
40
44
  provider: network.provider,
45
+ publicRead: network.server?.auth.public_read,
41
46
  serverMode: network.server?.mode,
42
47
  url: network.server?.url,
43
48
  rooms: network.rooms.map((room) => ({
44
49
  declaredMembers: [...room.members],
45
- id: room.id
50
+ id: room.id,
51
+ ...(room.visibility ? { visibility: room.visibility } : {}),
52
+ ...(room.write_policy ? { writePolicy: room.write_policy } : {})
46
53
  }))
47
54
  }));
48
55
  };
@@ -107,7 +114,11 @@ const toNetworkMemberView = (membership) => ({
107
114
  });
108
115
  const createNetworkKey = (provider, networkId) => `${provider}::${networkId}`;
109
116
  const buildNetworkDeclaration = (teamNode, network, roomMemberships) => ({
117
+ agentRegistration: network.server?.auth.agent_registration,
110
118
  authMode: network.server?.auth.mode,
119
+ consoleAnalytics: network.server?.mode === "managed"
120
+ ? network.server.console?.analytics?.provider
121
+ : undefined,
111
122
  debugEvents: network.server?.mode === "managed" ? network.server.debug_events : undefined,
112
123
  declaringTeamName: teamNode.name,
113
124
  declaringTeamSource: teamNode.source,
@@ -115,6 +126,7 @@ const buildNetworkDeclaration = (teamNode, network, roomMemberships) => ({
115
126
  expose: network.server?.mode === "managed" ? network.server.human_ingress : undefined,
116
127
  httpEnabled: network.server?.mode === "managed" ? network.server.human_ingress === true : false,
117
128
  name: network.name,
129
+ publicRead: network.server?.auth.public_read,
118
130
  serverMode: network.server?.mode,
119
131
  url: network.server?.url,
120
132
  rooms: network.rooms.map((room) => {
@@ -126,7 +138,9 @@ const buildNetworkDeclaration = (teamNode, network, roomMemberships) => ({
126
138
  return {
127
139
  declaredMembers: [...room.members],
128
140
  id: room.id,
129
- members: sortNetworkMembers(room.members, members)
141
+ members: sortNetworkMembers(room.members, members),
142
+ ...(room.visibility ? { visibility: room.visibility } : {}),
143
+ ...(room.write_policy ? { writePolicy: room.write_policy } : {})
130
144
  };
131
145
  })
132
146
  });
@@ -151,7 +165,9 @@ const buildNetworks = (plan) => {
151
165
  declaringTeamName: declaration.declaringTeamName,
152
166
  declaringTeamSource: declaration.declaringTeamSource,
153
167
  declarations: [declaration],
168
+ agentRegistration: declaration.agentRegistration,
154
169
  authMode: declaration.authMode,
170
+ consoleAnalytics: declaration.consoleAnalytics,
155
171
  debugEvents: declaration.debugEvents,
156
172
  directMessages: declaration.directMessages,
157
173
  expose: declaration.expose,
@@ -159,6 +175,7 @@ const buildNetworks = (plan) => {
159
175
  id: network.id,
160
176
  name: declaration.name,
161
177
  provider: network.provider,
178
+ publicRead: declaration.publicRead,
162
179
  serverMode: declaration.serverMode,
163
180
  url: declaration.url,
164
181
  rooms: declaration.rooms
@@ -171,11 +188,15 @@ const buildNetworks = (plan) => {
171
188
  if (firstDeclaration) {
172
189
  network.declaringTeamName = firstDeclaration.declaringTeamName;
173
190
  network.declaringTeamSource = firstDeclaration.declaringTeamSource;
191
+ network.agentRegistration = firstDeclaration.agentRegistration;
174
192
  network.authMode = firstDeclaration.authMode;
193
+ network.consoleAnalytics = firstDeclaration.consoleAnalytics;
194
+ network.debugEvents = firstDeclaration.debugEvents;
175
195
  network.directMessages = firstDeclaration.directMessages;
176
196
  network.expose = firstDeclaration.expose;
177
197
  network.httpEnabled = firstDeclaration.httpEnabled;
178
198
  network.name = firstDeclaration.name;
199
+ network.publicRead = firstDeclaration.publicRead;
179
200
  network.serverMode = firstDeclaration.serverMode;
180
201
  network.url = firstDeclaration.url;
181
202
  network.rooms = firstDeclaration.rooms;
@@ -41,6 +41,8 @@ const formatNetwork = (network, options) => {
41
41
  const getDeclarations = (network) => network.declarations ?? [
42
42
  {
43
43
  authMode: network.authMode,
44
+ agentRegistration: network.agentRegistration,
45
+ consoleAnalytics: network.consoleAnalytics,
44
46
  declaringTeamName: network.declaringTeamName,
45
47
  declaringTeamSource: network.declaringTeamSource,
46
48
  debugEvents: network.debugEvents,
@@ -48,6 +50,7 @@ const getDeclarations = (network) => network.declarations ?? [
48
50
  expose: network.expose,
49
51
  httpEnabled: network.httpEnabled,
50
52
  name: network.name,
53
+ publicRead: network.publicRead,
51
54
  rooms: network.rooms,
52
55
  serverMode: network.serverMode,
53
56
  url: network.url
@@ -58,6 +61,9 @@ const formatDeclaration = (network, declaration, options, projectRoot) => {
58
61
  declaration.serverMode ? `server=${declaration.serverMode}` : undefined,
59
62
  declaration.url ? `url=${declaration.url}` : undefined,
60
63
  declaration.authMode ? `auth=${declaration.authMode}` : undefined,
64
+ declaration.publicRead !== undefined ? `public_read=${declaration.publicRead}` : undefined,
65
+ declaration.agentRegistration ? `registration=${declaration.agentRegistration}` : undefined,
66
+ declaration.consoleAnalytics ? `analytics=${declaration.consoleAnalytics}` : undefined,
61
67
  declaration.directMessages === false ? "dms=disabled" : undefined,
62
68
  declaration.debugEvents === true ? "debug_events" : undefined,
63
69
  declaration.expose === true || declaration.httpEnabled ? "human_ingress" : undefined
@@ -88,7 +94,11 @@ export const renderOrganizationNetworks = (view, options = {}) => {
88
94
  const legacyDeclared = options.declared && !view.projectRoot
89
95
  ? ` room ${room.id} declared [${room.declaredMembers.join(", ")}]`
90
96
  : "";
91
- lines.push(`${declarationPrefix}${roomLast ? glyphs.last : glyphs.branch}#${room.id}${legacyDeclared}`);
97
+ const roomMetadata = [
98
+ room.visibility ? `visibility=${room.visibility}` : undefined,
99
+ room.writePolicy ? `write=${room.writePolicy}` : undefined
100
+ ].filter((entry) => entry !== undefined);
101
+ lines.push(`${declarationPrefix}${roomLast ? glyphs.last : glyphs.branch}#${room.id}${roomMetadata.length > 0 ? ` ${roomMetadata.join(" ")}` : ""}${legacyDeclared}`);
92
102
  if (options.declared) {
93
103
  const declaredMembers = room.declaredMembers.length > 0
94
104
  ? room.declaredMembers.join(", ")
@@ -28,12 +28,21 @@ const formatNetworkSummary = (network, options) => {
28
28
  network.serverMode ? `server=${network.serverMode}` : undefined,
29
29
  network.url ? `url=${network.url}` : undefined,
30
30
  network.authMode ? `auth=${network.authMode}` : undefined,
31
+ network.publicRead !== undefined ? `public_read=${network.publicRead}` : undefined,
32
+ network.agentRegistration ? `registration=${network.agentRegistration}` : undefined,
33
+ network.consoleAnalytics ? `analytics=${network.consoleAnalytics}` : undefined,
31
34
  network.directMessages === false ? "dms=disabled" : undefined,
32
35
  network.debugEvents === true ? "debug_events" : undefined,
33
36
  network.expose === true || network.httpEnabled ? "human_ingress" : undefined
34
37
  ].filter((entry) => entry !== undefined);
35
38
  const rooms = network.rooms
36
- .map((room) => `${room.id} [${room.declaredMembers.join(", ")}]`)
39
+ .map((room) => {
40
+ const roomMetadata = [
41
+ room.visibility ? `visibility=${room.visibility}` : undefined,
42
+ room.writePolicy ? `write=${room.writePolicy}` : undefined
43
+ ].filter((entry) => entry !== undefined);
44
+ return `${room.id}${roomMetadata.length > 0 ? ` ${roomMetadata.join(" ")}` : ""} [${room.declaredMembers.join(", ")}]`;
45
+ })
37
46
  .join("; ");
38
47
  return `network ${id} "${network.name}"${metadata.length > 0 ? ` ${metadata.join(" ")}` : ""}: ${rooms}`;
39
48
  };
@@ -15,9 +15,13 @@ export interface OrganizationViewTreeNode {
15
15
  export interface OrganizationTreeNetworkRoomSummary {
16
16
  declaredMembers: string[];
17
17
  id: string;
18
+ visibility?: "public" | "private";
19
+ writePolicy?: "members" | "operators" | "registered_agents";
18
20
  }
19
21
  export interface OrganizationTreeNetworkSummary {
22
+ agentRegistration?: "disabled" | "open" | "token";
20
23
  authMode?: "bearer" | "none" | "open";
24
+ consoleAnalytics?: string;
21
25
  debugEvents?: boolean;
22
26
  directMessages?: boolean;
23
27
  expose?: boolean;
@@ -25,6 +29,7 @@ export interface OrganizationTreeNetworkSummary {
25
29
  id: string;
26
30
  name: string;
27
31
  provider: "moltnet";
32
+ publicRead?: boolean;
28
33
  serverMode?: "external" | "managed";
29
34
  url?: string;
30
35
  rooms: OrganizationTreeNetworkRoomSummary[];
@@ -48,7 +53,9 @@ export interface OrganizationNetworkMemberView {
48
53
  representativePath?: string[];
49
54
  }
50
55
  export interface OrganizationNetworkDeclarationView {
56
+ agentRegistration?: "disabled" | "open" | "token";
51
57
  authMode?: "bearer" | "none" | "open";
58
+ consoleAnalytics?: string;
52
59
  debugEvents?: boolean;
53
60
  declaringTeamName: string;
54
61
  declaringTeamSource: string;
@@ -56,6 +63,7 @@ export interface OrganizationNetworkDeclarationView {
56
63
  expose?: boolean;
57
64
  httpEnabled?: boolean;
58
65
  name: string;
66
+ publicRead?: boolean;
59
67
  rooms: OrganizationNetworkRoomView[];
60
68
  serverMode?: "external" | "managed";
61
69
  url?: string;
@@ -64,9 +72,13 @@ export interface OrganizationNetworkRoomView {
64
72
  declaredMembers: string[];
65
73
  id: string;
66
74
  members: OrganizationNetworkMemberView[];
75
+ visibility?: "public" | "private";
76
+ writePolicy?: "members" | "operators" | "registered_agents";
67
77
  }
68
78
  export interface OrganizationNetworkView {
79
+ agentRegistration?: "disabled" | "open" | "token";
69
80
  authMode?: "bearer" | "none" | "open";
81
+ consoleAnalytics?: string;
70
82
  debugEvents?: boolean;
71
83
  declaringTeamName: string;
72
84
  declaringTeamSource: string;
@@ -76,6 +88,7 @@ export interface OrganizationNetworkView {
76
88
  id: string;
77
89
  name: string;
78
90
  provider: "moltnet";
91
+ publicRead?: boolean;
79
92
  rooms: OrganizationNetworkRoomView[];
80
93
  serverMode?: "external" | "managed";
81
94
  url?: string;
@@ -35,6 +35,8 @@ const orderTeamAuth = (server) => withDefinedEntries([
35
35
  "auth",
36
36
  withDefinedEntries([
37
37
  ["mode", server.auth.mode],
38
+ ["public_read", server.auth.public_read],
39
+ ["agent_registration", server.auth.agent_registration],
38
40
  ["tokens", server.auth.tokens?.map(orderTeamAuthToken)],
39
41
  [
40
42
  "client",
@@ -49,6 +51,7 @@ const orderTeamAuth = (server) => withDefinedEntries([
49
51
  ]
50
52
  ])
51
53
  ],
54
+ ["console", server.mode === "managed" ? server.console : undefined],
52
55
  ["store", orderTeamStore(server)],
53
56
  [
54
57
  "pairings",
@@ -75,6 +78,8 @@ export const orderTeamNetworks = (networks) => networks?.map((network) => withDe
75
78
  network.rooms.map((room) => withDefinedEntries([
76
79
  ["id", room.id],
77
80
  ["name", room.name],
81
+ ["visibility", room.visibility],
82
+ ["write_policy", room.write_policy],
78
83
  ["members", room.members]
79
84
  ]))
80
85
  ]
@@ -630,10 +630,24 @@ declare const teamManifestSchema: z.ZodObject<{
630
630
  id: z.ZodString;
631
631
  members: z.ZodArray<z.ZodString>;
632
632
  name: z.ZodOptional<z.ZodString>;
633
+ visibility: z.ZodOptional<z.ZodEnum<{
634
+ public: "public";
635
+ private: "private";
636
+ }>>;
637
+ write_policy: z.ZodOptional<z.ZodEnum<{
638
+ members: "members";
639
+ registered_agents: "registered_agents";
640
+ operators: "operators";
641
+ }>>;
633
642
  }, z.core.$strict>>;
634
643
  server: z.ZodDiscriminatedUnion<[z.ZodObject<{
635
644
  allowed_origins: z.ZodOptional<z.ZodArray<z.ZodString>>;
636
645
  auth: z.ZodObject<{
646
+ agent_registration: z.ZodOptional<z.ZodEnum<{
647
+ token: "token";
648
+ disabled: "disabled";
649
+ open: "open";
650
+ }>>;
637
651
  client: z.ZodOptional<z.ZodObject<{
638
652
  static_token: z.ZodOptional<z.ZodBoolean>;
639
653
  token_env: z.ZodOptional<z.ZodString>;
@@ -645,6 +659,7 @@ declare const teamManifestSchema: z.ZodObject<{
645
659
  open: "open";
646
660
  bearer: "bearer";
647
661
  }>;
662
+ public_read: z.ZodOptional<z.ZodBoolean>;
648
663
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
649
664
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
650
665
  id: z.ZodString;
@@ -658,6 +673,12 @@ declare const teamManifestSchema: z.ZodObject<{
658
673
  }>>;
659
674
  }, z.core.$strict>>>;
660
675
  }, z.core.$strict>;
676
+ console: z.ZodOptional<z.ZodObject<{
677
+ analytics: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
678
+ provider: z.ZodLiteral<"google">;
679
+ measurement_id: z.ZodString;
680
+ }, z.core.$strict>], "provider">>;
681
+ }, z.core.$strict>>;
661
682
  debug_events: z.ZodOptional<z.ZodBoolean>;
662
683
  direct_messages: z.ZodOptional<z.ZodBoolean>;
663
684
  human_ingress: z.ZodOptional<z.ZodBoolean>;
@@ -705,6 +726,11 @@ declare const teamManifestSchema: z.ZodObject<{
705
726
  url: z.ZodOptional<z.ZodString>;
706
727
  }, z.core.$strict>, z.ZodObject<{
707
728
  auth: z.ZodObject<{
729
+ agent_registration: z.ZodOptional<z.ZodEnum<{
730
+ token: "token";
731
+ disabled: "disabled";
732
+ open: "open";
733
+ }>>;
708
734
  client: z.ZodOptional<z.ZodObject<{
709
735
  static_token: z.ZodOptional<z.ZodBoolean>;
710
736
  token_env: z.ZodOptional<z.ZodString>;
@@ -716,6 +742,7 @@ declare const teamManifestSchema: z.ZodObject<{
716
742
  open: "open";
717
743
  bearer: "bearer";
718
744
  }>;
745
+ public_read: z.ZodOptional<z.ZodBoolean>;
719
746
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
720
747
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
721
748
  id: z.ZodString;
@@ -1286,10 +1313,24 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1286
1313
  id: z.ZodString;
1287
1314
  members: z.ZodArray<z.ZodString>;
1288
1315
  name: z.ZodOptional<z.ZodString>;
1316
+ visibility: z.ZodOptional<z.ZodEnum<{
1317
+ public: "public";
1318
+ private: "private";
1319
+ }>>;
1320
+ write_policy: z.ZodOptional<z.ZodEnum<{
1321
+ members: "members";
1322
+ registered_agents: "registered_agents";
1323
+ operators: "operators";
1324
+ }>>;
1289
1325
  }, z.core.$strict>>;
1290
1326
  server: z.ZodDiscriminatedUnion<[z.ZodObject<{
1291
1327
  allowed_origins: z.ZodOptional<z.ZodArray<z.ZodString>>;
1292
1328
  auth: z.ZodObject<{
1329
+ agent_registration: z.ZodOptional<z.ZodEnum<{
1330
+ token: "token";
1331
+ disabled: "disabled";
1332
+ open: "open";
1333
+ }>>;
1293
1334
  client: z.ZodOptional<z.ZodObject<{
1294
1335
  static_token: z.ZodOptional<z.ZodBoolean>;
1295
1336
  token_env: z.ZodOptional<z.ZodString>;
@@ -1301,6 +1342,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1301
1342
  open: "open";
1302
1343
  bearer: "bearer";
1303
1344
  }>;
1345
+ public_read: z.ZodOptional<z.ZodBoolean>;
1304
1346
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
1305
1347
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
1306
1348
  id: z.ZodString;
@@ -1314,6 +1356,12 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1314
1356
  }>>;
1315
1357
  }, z.core.$strict>>>;
1316
1358
  }, z.core.$strict>;
1359
+ console: z.ZodOptional<z.ZodObject<{
1360
+ analytics: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
1361
+ provider: z.ZodLiteral<"google">;
1362
+ measurement_id: z.ZodString;
1363
+ }, z.core.$strict>], "provider">>;
1364
+ }, z.core.$strict>>;
1317
1365
  debug_events: z.ZodOptional<z.ZodBoolean>;
1318
1366
  direct_messages: z.ZodOptional<z.ZodBoolean>;
1319
1367
  human_ingress: z.ZodOptional<z.ZodBoolean>;
@@ -1361,6 +1409,11 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1361
1409
  url: z.ZodOptional<z.ZodString>;
1362
1410
  }, z.core.$strict>, z.ZodObject<{
1363
1411
  auth: z.ZodObject<{
1412
+ agent_registration: z.ZodOptional<z.ZodEnum<{
1413
+ token: "token";
1414
+ disabled: "disabled";
1415
+ open: "open";
1416
+ }>>;
1364
1417
  client: z.ZodOptional<z.ZodObject<{
1365
1418
  static_token: z.ZodOptional<z.ZodBoolean>;
1366
1419
  token_env: z.ZodOptional<z.ZodString>;
@@ -1372,6 +1425,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1372
1425
  open: "open";
1373
1426
  bearer: "bearer";
1374
1427
  }>;
1428
+ public_read: z.ZodOptional<z.ZodBoolean>;
1375
1429
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
1376
1430
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
1377
1431
  id: z.ZodString;
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ export declare const teamNetworkAgentRegistrationSchema: z.ZodEnum<{
3
+ token: "token";
4
+ disabled: "disabled";
5
+ open: "open";
6
+ }>;
7
+ export declare const teamNetworkRoomVisibilitySchema: z.ZodEnum<{
8
+ public: "public";
9
+ private: "private";
10
+ }>;
11
+ export declare const teamNetworkRoomWritePolicySchema: z.ZodEnum<{
12
+ members: "members";
13
+ registered_agents: "registered_agents";
14
+ operators: "operators";
15
+ }>;
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ export const teamNetworkAgentRegistrationSchema = z.enum(["disabled", "token", "open"]);
3
+ export const teamNetworkRoomVisibilitySchema = z.enum(["public", "private"]);
4
+ export const teamNetworkRoomWritePolicySchema = z.enum([
5
+ "members",
6
+ "registered_agents",
7
+ "operators"
8
+ ]);
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ export declare const teamNetworkConsoleSchema: z.ZodObject<{
3
+ analytics: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
4
+ provider: z.ZodLiteral<"google">;
5
+ measurement_id: z.ZodString;
6
+ }, z.core.$strict>], "provider">>;
7
+ }, z.core.$strict>;
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ const teamNetworkConsoleAnalyticsSchema = z
3
+ .discriminatedUnion("provider", [
4
+ z
5
+ .object({
6
+ provider: z.literal("google"),
7
+ measurement_id: z
8
+ .string()
9
+ .trim()
10
+ .min(1)
11
+ .regex(/^G-[A-Za-z0-9_-]+$/, "measurement_id must be a Google Analytics measurement ID")
12
+ })
13
+ .strict()
14
+ ]);
15
+ export const teamNetworkConsoleSchema = z
16
+ .object({
17
+ analytics: teamNetworkConsoleAnalyticsSchema.optional()
18
+ })
19
+ .strict();
@@ -2,6 +2,11 @@ import { z } from "zod";
2
2
  export { teamWorkspaceDocsSchema, teamWorkspaceSchema } from "./workspaceSchemas.js";
3
3
  export type { TeamWorkspace, TeamWorkspaceDocs, TeamWorkspaceResource, TeamWorkspaceResource as TeamNetworkResource } from "./workspaceSchemas.js";
4
4
  declare const teamNetworkAuthSchema: z.ZodObject<{
5
+ agent_registration: z.ZodOptional<z.ZodEnum<{
6
+ token: "token";
7
+ disabled: "disabled";
8
+ open: "open";
9
+ }>>;
5
10
  client: z.ZodOptional<z.ZodObject<{
6
11
  static_token: z.ZodOptional<z.ZodBoolean>;
7
12
  token_env: z.ZodOptional<z.ZodString>;
@@ -13,6 +18,7 @@ declare const teamNetworkAuthSchema: z.ZodObject<{
13
18
  open: "open";
14
19
  bearer: "bearer";
15
20
  }>;
21
+ public_read: z.ZodOptional<z.ZodBoolean>;
16
22
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
17
23
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
18
24
  id: z.ZodString;
@@ -57,6 +63,11 @@ declare const teamNetworkStoreSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
57
63
  declare const teamNetworkServerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
58
64
  allowed_origins: z.ZodOptional<z.ZodArray<z.ZodString>>;
59
65
  auth: z.ZodObject<{
66
+ agent_registration: z.ZodOptional<z.ZodEnum<{
67
+ token: "token";
68
+ disabled: "disabled";
69
+ open: "open";
70
+ }>>;
60
71
  client: z.ZodOptional<z.ZodObject<{
61
72
  static_token: z.ZodOptional<z.ZodBoolean>;
62
73
  token_env: z.ZodOptional<z.ZodString>;
@@ -68,6 +79,7 @@ declare const teamNetworkServerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
68
79
  open: "open";
69
80
  bearer: "bearer";
70
81
  }>;
82
+ public_read: z.ZodOptional<z.ZodBoolean>;
71
83
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
72
84
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
73
85
  id: z.ZodString;
@@ -81,6 +93,12 @@ declare const teamNetworkServerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
81
93
  }>>;
82
94
  }, z.core.$strict>>>;
83
95
  }, z.core.$strict>;
96
+ console: z.ZodOptional<z.ZodObject<{
97
+ analytics: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
98
+ provider: z.ZodLiteral<"google">;
99
+ measurement_id: z.ZodString;
100
+ }, z.core.$strict>], "provider">>;
101
+ }, z.core.$strict>>;
84
102
  debug_events: z.ZodOptional<z.ZodBoolean>;
85
103
  direct_messages: z.ZodOptional<z.ZodBoolean>;
86
104
  human_ingress: z.ZodOptional<z.ZodBoolean>;
@@ -128,6 +146,11 @@ declare const teamNetworkServerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
128
146
  url: z.ZodOptional<z.ZodString>;
129
147
  }, z.core.$strict>, z.ZodObject<{
130
148
  auth: z.ZodObject<{
149
+ agent_registration: z.ZodOptional<z.ZodEnum<{
150
+ token: "token";
151
+ disabled: "disabled";
152
+ open: "open";
153
+ }>>;
131
154
  client: z.ZodOptional<z.ZodObject<{
132
155
  static_token: z.ZodOptional<z.ZodBoolean>;
133
156
  token_env: z.ZodOptional<z.ZodString>;
@@ -139,6 +162,7 @@ declare const teamNetworkServerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
139
162
  open: "open";
140
163
  bearer: "bearer";
141
164
  }>;
165
+ public_read: z.ZodOptional<z.ZodBoolean>;
142
166
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
143
167
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
144
168
  id: z.ZodString;
@@ -159,6 +183,15 @@ declare const teamNetworkRoomSchema: z.ZodObject<{
159
183
  id: z.ZodString;
160
184
  members: z.ZodArray<z.ZodString>;
161
185
  name: z.ZodOptional<z.ZodString>;
186
+ visibility: z.ZodOptional<z.ZodEnum<{
187
+ public: "public";
188
+ private: "private";
189
+ }>>;
190
+ write_policy: z.ZodOptional<z.ZodEnum<{
191
+ members: "members";
192
+ registered_agents: "registered_agents";
193
+ operators: "operators";
194
+ }>>;
162
195
  }, z.core.$strict>;
163
196
  export declare const teamNetworkSchema: z.ZodObject<{
164
197
  id: z.ZodString;
@@ -168,10 +201,24 @@ export declare const teamNetworkSchema: z.ZodObject<{
168
201
  id: z.ZodString;
169
202
  members: z.ZodArray<z.ZodString>;
170
203
  name: z.ZodOptional<z.ZodString>;
204
+ visibility: z.ZodOptional<z.ZodEnum<{
205
+ public: "public";
206
+ private: "private";
207
+ }>>;
208
+ write_policy: z.ZodOptional<z.ZodEnum<{
209
+ members: "members";
210
+ registered_agents: "registered_agents";
211
+ operators: "operators";
212
+ }>>;
171
213
  }, z.core.$strict>>;
172
214
  server: z.ZodDiscriminatedUnion<[z.ZodObject<{
173
215
  allowed_origins: z.ZodOptional<z.ZodArray<z.ZodString>>;
174
216
  auth: z.ZodObject<{
217
+ agent_registration: z.ZodOptional<z.ZodEnum<{
218
+ token: "token";
219
+ disabled: "disabled";
220
+ open: "open";
221
+ }>>;
175
222
  client: z.ZodOptional<z.ZodObject<{
176
223
  static_token: z.ZodOptional<z.ZodBoolean>;
177
224
  token_env: z.ZodOptional<z.ZodString>;
@@ -183,6 +230,7 @@ export declare const teamNetworkSchema: z.ZodObject<{
183
230
  open: "open";
184
231
  bearer: "bearer";
185
232
  }>;
233
+ public_read: z.ZodOptional<z.ZodBoolean>;
186
234
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
187
235
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
188
236
  id: z.ZodString;
@@ -196,6 +244,12 @@ export declare const teamNetworkSchema: z.ZodObject<{
196
244
  }>>;
197
245
  }, z.core.$strict>>>;
198
246
  }, z.core.$strict>;
247
+ console: z.ZodOptional<z.ZodObject<{
248
+ analytics: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
249
+ provider: z.ZodLiteral<"google">;
250
+ measurement_id: z.ZodString;
251
+ }, z.core.$strict>], "provider">>;
252
+ }, z.core.$strict>>;
199
253
  debug_events: z.ZodOptional<z.ZodBoolean>;
200
254
  direct_messages: z.ZodOptional<z.ZodBoolean>;
201
255
  human_ingress: z.ZodOptional<z.ZodBoolean>;
@@ -243,6 +297,11 @@ export declare const teamNetworkSchema: z.ZodObject<{
243
297
  url: z.ZodOptional<z.ZodString>;
244
298
  }, z.core.$strict>, z.ZodObject<{
245
299
  auth: z.ZodObject<{
300
+ agent_registration: z.ZodOptional<z.ZodEnum<{
301
+ token: "token";
302
+ disabled: "disabled";
303
+ open: "open";
304
+ }>>;
246
305
  client: z.ZodOptional<z.ZodObject<{
247
306
  static_token: z.ZodOptional<z.ZodBoolean>;
248
307
  token_env: z.ZodOptional<z.ZodString>;
@@ -254,6 +313,7 @@ export declare const teamNetworkSchema: z.ZodObject<{
254
313
  open: "open";
255
314
  bearer: "bearer";
256
315
  }>;
316
+ public_read: z.ZodOptional<z.ZodBoolean>;
257
317
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
258
318
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
259
319
  id: z.ZodString;
@@ -1,4 +1,6 @@
1
1
  import { z } from "zod";
2
+ import { teamNetworkAgentRegistrationSchema, teamNetworkRoomVisibilitySchema, teamNetworkRoomWritePolicySchema } from "./teamNetworkAccessSchemas.js";
3
+ import { teamNetworkConsoleSchema } from "./teamNetworkConsoleSchemas.js";
2
4
  export { teamWorkspaceDocsSchema, teamWorkspaceSchema } from "./workspaceSchemas.js";
3
5
  const moltnetScopeSchema = z.enum(["observe", "write", "admin", "attach", "pair"]);
4
6
  const countTruthy = (value) => value.filter((entry) => Boolean(entry)).length;
@@ -48,8 +50,10 @@ const teamNetworkAuthClientSchema = z
48
50
  });
49
51
  const teamNetworkAuthSchema = z
50
52
  .object({
53
+ agent_registration: teamNetworkAgentRegistrationSchema.optional(),
51
54
  client: teamNetworkAuthClientSchema.optional(),
52
55
  mode: z.enum(["none", "bearer", "open"]),
56
+ public_read: z.boolean().optional(),
53
57
  tokens: z.array(teamNetworkAuthTokenSchema).optional()
54
58
  })
55
59
  .strict()
@@ -69,10 +73,10 @@ const teamNetworkAuthSchema = z
69
73
  }
70
74
  return;
71
75
  }
72
- if (value.mode === "bearer" && !value.client) {
76
+ if (value.mode === "bearer" && !value.client && value.agent_registration !== "open") {
73
77
  context.addIssue({
74
78
  code: z.ZodIssueCode.custom,
75
- message: "auth.mode bearer requires auth.client"
79
+ message: "auth.mode bearer requires auth.client unless agent_registration is open"
76
80
  });
77
81
  }
78
82
  if (value.mode === "open" && value.client) {
@@ -94,6 +98,9 @@ const teamNetworkAuthSchema = z
94
98
  });
95
99
  }
96
100
  }
101
+ if (value.mode === "open" && value.agent_registration && value.agent_registration !== "open") {
102
+ context.addIssue({ code: z.ZodIssueCode.custom, message: "auth.mode open requires agent_registration open when declared" });
103
+ }
97
104
  if (value.tokens) {
98
105
  const tokenIds = value.tokens.map((token) => token.id);
99
106
  if (new Set(tokenIds).size !== tokenIds.length) {
@@ -182,6 +189,7 @@ const teamNetworkManagedServerSchema = z
182
189
  .object({
183
190
  allowed_origins: z.array(z.string().trim().min(1)).optional(),
184
191
  auth: teamNetworkAuthSchema,
192
+ console: teamNetworkConsoleSchema.optional(),
185
193
  debug_events: z.boolean().optional(),
186
194
  direct_messages: z.boolean().optional(),
187
195
  human_ingress: z.boolean().optional(),
@@ -201,13 +209,13 @@ const teamNetworkManagedServerSchema = z
201
209
  message: "managed server with auth.mode bearer requires at least one auth token"
202
210
  });
203
211
  }
204
- if (!value.auth.client?.token_id) {
212
+ if (value.auth.client && !value.auth.client.token_id) {
205
213
  context.addIssue({
206
214
  code: z.ZodIssueCode.custom,
207
215
  message: "managed bearer auth requires auth.client.token_id"
208
216
  });
209
217
  }
210
- if (value.auth.client?.token_env || value.auth.client?.token_path) {
218
+ if (value.auth.client && (value.auth.client.token_env || value.auth.client.token_path)) {
211
219
  context.addIssue({
212
220
  code: z.ZodIssueCode.custom,
213
221
  message: "managed bearer auth requires auth.client.token_id"
@@ -243,6 +251,9 @@ const teamNetworkManagedServerSchema = z
243
251
  message: "managed open auth client token source must be token_id"
244
252
  });
245
253
  }
254
+ if (value.auth.client.token_id && !value.auth.tokens?.some((token) => token.id === value.auth.client?.token_id)) {
255
+ context.addIssue({ code: z.ZodIssueCode.custom, message: `managed open auth references unknown token: ${value.auth.client.token_id}` });
256
+ }
246
257
  }
247
258
  });
248
259
  const teamNetworkExternalServerSchema = z
@@ -287,7 +298,7 @@ const teamNetworkExternalServerSchema = z
287
298
  message: "external open auth requires token_env or token_path"
288
299
  });
289
300
  }
290
- if (value.auth.mode === "bearer") {
301
+ if (value.auth.mode === "bearer" && value.auth.client) {
291
302
  const sourceCount = countTruthy([
292
303
  value.auth.client.token_id,
293
304
  value.auth.client.token_env,
@@ -308,8 +319,10 @@ const teamNetworkServerSchema = z.discriminatedUnion("mode", [
308
319
  const teamNetworkRoomSchema = z
309
320
  .object({
310
321
  id: z.string().trim().min(1),
311
- members: z.array(z.string().trim().min(1)).min(1),
312
- name: z.string().trim().min(1).optional()
322
+ members: z.array(z.string().trim().min(1)),
323
+ name: z.string().trim().min(1).optional(),
324
+ visibility: teamNetworkRoomVisibilitySchema.optional(),
325
+ write_policy: teamNetworkRoomWritePolicySchema.optional()
313
326
  })
314
327
  .strict();
315
328
  export const teamNetworkSchema = z
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spawnfile",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Canonical source compiler for autonomous agents and teams.",
5
5
  "license": "MIT",
6
6
  "type": "module",