spawnfile 0.1.9 → 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,8 +135,10 @@ 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 } : {}),
141
+ ...(server.debug_events !== undefined ? { debug_events: server.debug_events } : {}),
134
142
  ...(server.trust_forwarded_proto !== undefined
135
143
  ? { trust_forwarded_proto: server.trust_forwarded_proto }
136
144
  : {}),
@@ -138,12 +146,16 @@ export const createMoltnetNativeServerConfig = ({ networkId, networkName, rooms,
138
146
  },
139
147
  auth: {
140
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 } : {}),
141
151
  ...(tokens.length > 0 ? { tokens } : {})
142
152
  },
143
153
  storage: storageConfigFor(networkId, server.store),
144
154
  rooms: rooms.map((room) => ({
145
155
  id: room.id,
146
156
  ...(room.name ? { name: room.name } : {}),
157
+ ...(room.visibility ? { visibility: room.visibility } : {}),
158
+ ...(room.write_policy ? { write_policy: room.write_policy } : {}),
147
159
  members: room.members
148
160
  })),
149
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,18 +30,26 @@ 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,
38
+ debugEvents: network.server?.mode === "managed" ? network.server.debug_events : undefined,
34
39
  directMessages: network.server?.mode === "managed" ? network.server.direct_messages : undefined,
35
40
  expose: network.server?.mode === "managed" ? network.server.human_ingress : undefined,
36
41
  httpEnabled: network.server?.mode === "managed" ? network.server.human_ingress === true : false,
37
42
  id: network.id,
38
43
  name: network.name,
39
44
  provider: network.provider,
45
+ publicRead: network.server?.auth.public_read,
40
46
  serverMode: network.server?.mode,
41
47
  url: network.server?.url,
42
48
  rooms: network.rooms.map((room) => ({
43
49
  declaredMembers: [...room.members],
44
- id: room.id
50
+ id: room.id,
51
+ ...(room.visibility ? { visibility: room.visibility } : {}),
52
+ ...(room.write_policy ? { writePolicy: room.write_policy } : {})
45
53
  }))
46
54
  }));
47
55
  };
@@ -106,13 +114,19 @@ const toNetworkMemberView = (membership) => ({
106
114
  });
107
115
  const createNetworkKey = (provider, networkId) => `${provider}::${networkId}`;
108
116
  const buildNetworkDeclaration = (teamNode, network, roomMemberships) => ({
117
+ agentRegistration: network.server?.auth.agent_registration,
109
118
  authMode: network.server?.auth.mode,
119
+ consoleAnalytics: network.server?.mode === "managed"
120
+ ? network.server.console?.analytics?.provider
121
+ : undefined,
122
+ debugEvents: network.server?.mode === "managed" ? network.server.debug_events : undefined,
110
123
  declaringTeamName: teamNode.name,
111
124
  declaringTeamSource: teamNode.source,
112
125
  directMessages: network.server?.mode === "managed" ? network.server.direct_messages : undefined,
113
126
  expose: network.server?.mode === "managed" ? network.server.human_ingress : undefined,
114
127
  httpEnabled: network.server?.mode === "managed" ? network.server.human_ingress === true : false,
115
128
  name: network.name,
129
+ publicRead: network.server?.auth.public_read,
116
130
  serverMode: network.server?.mode,
117
131
  url: network.server?.url,
118
132
  rooms: network.rooms.map((room) => {
@@ -124,7 +138,9 @@ const buildNetworkDeclaration = (teamNode, network, roomMemberships) => ({
124
138
  return {
125
139
  declaredMembers: [...room.members],
126
140
  id: room.id,
127
- 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 } : {})
128
144
  };
129
145
  })
130
146
  });
@@ -149,13 +165,17 @@ const buildNetworks = (plan) => {
149
165
  declaringTeamName: declaration.declaringTeamName,
150
166
  declaringTeamSource: declaration.declaringTeamSource,
151
167
  declarations: [declaration],
168
+ agentRegistration: declaration.agentRegistration,
152
169
  authMode: declaration.authMode,
170
+ consoleAnalytics: declaration.consoleAnalytics,
171
+ debugEvents: declaration.debugEvents,
153
172
  directMessages: declaration.directMessages,
154
173
  expose: declaration.expose,
155
174
  httpEnabled: declaration.httpEnabled,
156
175
  id: network.id,
157
176
  name: declaration.name,
158
177
  provider: network.provider,
178
+ publicRead: declaration.publicRead,
159
179
  serverMode: declaration.serverMode,
160
180
  url: declaration.url,
161
181
  rooms: declaration.rooms
@@ -168,11 +188,15 @@ const buildNetworks = (plan) => {
168
188
  if (firstDeclaration) {
169
189
  network.declaringTeamName = firstDeclaration.declaringTeamName;
170
190
  network.declaringTeamSource = firstDeclaration.declaringTeamSource;
191
+ network.agentRegistration = firstDeclaration.agentRegistration;
171
192
  network.authMode = firstDeclaration.authMode;
193
+ network.consoleAnalytics = firstDeclaration.consoleAnalytics;
194
+ network.debugEvents = firstDeclaration.debugEvents;
172
195
  network.directMessages = firstDeclaration.directMessages;
173
196
  network.expose = firstDeclaration.expose;
174
197
  network.httpEnabled = firstDeclaration.httpEnabled;
175
198
  network.name = firstDeclaration.name;
199
+ network.publicRead = firstDeclaration.publicRead;
176
200
  network.serverMode = firstDeclaration.serverMode;
177
201
  network.url = firstDeclaration.url;
178
202
  network.rooms = firstDeclaration.rooms;
@@ -41,12 +41,16 @@ 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,
48
+ debugEvents: network.debugEvents,
46
49
  directMessages: network.directMessages,
47
50
  expose: network.expose,
48
51
  httpEnabled: network.httpEnabled,
49
52
  name: network.name,
53
+ publicRead: network.publicRead,
50
54
  rooms: network.rooms,
51
55
  serverMode: network.serverMode,
52
56
  url: network.url
@@ -57,7 +61,11 @@ const formatDeclaration = (network, declaration, options, projectRoot) => {
57
61
  declaration.serverMode ? `server=${declaration.serverMode}` : undefined,
58
62
  declaration.url ? `url=${declaration.url}` : undefined,
59
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,
60
67
  declaration.directMessages === false ? "dms=disabled" : undefined,
68
+ declaration.debugEvents === true ? "debug_events" : undefined,
61
69
  declaration.expose === true || declaration.httpEnabled ? "human_ingress" : undefined
62
70
  ].filter((entry) => entry !== undefined);
63
71
  const source = options.paths
@@ -86,7 +94,11 @@ export const renderOrganizationNetworks = (view, options = {}) => {
86
94
  const legacyDeclared = options.declared && !view.projectRoot
87
95
  ? ` room ${room.id} declared [${room.declaredMembers.join(", ")}]`
88
96
  : "";
89
- 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}`);
90
102
  if (options.declared) {
91
103
  const declaredMembers = room.declaredMembers.length > 0
92
104
  ? room.declaredMembers.join(", ")
@@ -28,11 +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,
35
+ network.debugEvents === true ? "debug_events" : undefined,
32
36
  network.expose === true || network.httpEnabled ? "human_ingress" : undefined
33
37
  ].filter((entry) => entry !== undefined);
34
38
  const rooms = network.rooms
35
- .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
+ })
36
46
  .join("; ");
37
47
  return `network ${id} "${network.name}"${metadata.length > 0 ? ` ${metadata.join(" ")}` : ""}: ${rooms}`;
38
48
  };
@@ -15,15 +15,21 @@ 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;
25
+ debugEvents?: boolean;
21
26
  directMessages?: boolean;
22
27
  expose?: boolean;
23
28
  httpEnabled?: boolean;
24
29
  id: string;
25
30
  name: string;
26
31
  provider: "moltnet";
32
+ publicRead?: boolean;
27
33
  serverMode?: "external" | "managed";
28
34
  url?: string;
29
35
  rooms: OrganizationTreeNetworkRoomSummary[];
@@ -47,13 +53,17 @@ export interface OrganizationNetworkMemberView {
47
53
  representativePath?: string[];
48
54
  }
49
55
  export interface OrganizationNetworkDeclarationView {
56
+ agentRegistration?: "disabled" | "open" | "token";
50
57
  authMode?: "bearer" | "none" | "open";
58
+ consoleAnalytics?: string;
59
+ debugEvents?: boolean;
51
60
  declaringTeamName: string;
52
61
  declaringTeamSource: string;
53
62
  directMessages?: boolean;
54
63
  expose?: boolean;
55
64
  httpEnabled?: boolean;
56
65
  name: string;
66
+ publicRead?: boolean;
57
67
  rooms: OrganizationNetworkRoomView[];
58
68
  serverMode?: "external" | "managed";
59
69
  url?: string;
@@ -62,9 +72,14 @@ export interface OrganizationNetworkRoomView {
62
72
  declaredMembers: string[];
63
73
  id: string;
64
74
  members: OrganizationNetworkMemberView[];
75
+ visibility?: "public" | "private";
76
+ writePolicy?: "members" | "operators" | "registered_agents";
65
77
  }
66
78
  export interface OrganizationNetworkView {
79
+ agentRegistration?: "disabled" | "open" | "token";
67
80
  authMode?: "bearer" | "none" | "open";
81
+ consoleAnalytics?: string;
82
+ debugEvents?: boolean;
68
83
  declaringTeamName: string;
69
84
  declaringTeamSource: string;
70
85
  directMessages?: boolean;
@@ -73,6 +88,7 @@ export interface OrganizationNetworkView {
73
88
  id: string;
74
89
  name: string;
75
90
  provider: "moltnet";
91
+ publicRead?: boolean;
76
92
  rooms: OrganizationNetworkRoomView[];
77
93
  serverMode?: "external" | "managed";
78
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",
@@ -58,6 +61,7 @@ const orderTeamAuth = (server) => withDefinedEntries([
58
61
  ],
59
62
  ["human_ingress", server.mode === "managed" ? server.human_ingress : undefined],
60
63
  ["direct_messages", server.mode === "managed" ? server.direct_messages : undefined],
64
+ ["debug_events", server.mode === "managed" ? server.debug_events : undefined],
61
65
  [
62
66
  "trust_forwarded_proto",
63
67
  server.mode === "managed" ? server.trust_forwarded_proto : undefined
@@ -74,6 +78,8 @@ export const orderTeamNetworks = (networks) => networks?.map((network) => withDe
74
78
  network.rooms.map((room) => withDefinedEntries([
75
79
  ["id", room.id],
76
80
  ["name", room.name],
81
+ ["visibility", room.visibility],
82
+ ["write_policy", room.write_policy],
77
83
  ["members", room.members]
78
84
  ]))
79
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,13 @@ 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>>;
682
+ debug_events: z.ZodOptional<z.ZodBoolean>;
661
683
  direct_messages: z.ZodOptional<z.ZodBoolean>;
662
684
  human_ingress: z.ZodOptional<z.ZodBoolean>;
663
685
  listen: z.ZodObject<{
@@ -704,6 +726,11 @@ declare const teamManifestSchema: z.ZodObject<{
704
726
  url: z.ZodOptional<z.ZodString>;
705
727
  }, z.core.$strict>, z.ZodObject<{
706
728
  auth: z.ZodObject<{
729
+ agent_registration: z.ZodOptional<z.ZodEnum<{
730
+ token: "token";
731
+ disabled: "disabled";
732
+ open: "open";
733
+ }>>;
707
734
  client: z.ZodOptional<z.ZodObject<{
708
735
  static_token: z.ZodOptional<z.ZodBoolean>;
709
736
  token_env: z.ZodOptional<z.ZodString>;
@@ -715,6 +742,7 @@ declare const teamManifestSchema: z.ZodObject<{
715
742
  open: "open";
716
743
  bearer: "bearer";
717
744
  }>;
745
+ public_read: z.ZodOptional<z.ZodBoolean>;
718
746
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
719
747
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
720
748
  id: z.ZodString;
@@ -1285,10 +1313,24 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1285
1313
  id: z.ZodString;
1286
1314
  members: z.ZodArray<z.ZodString>;
1287
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
+ }>>;
1288
1325
  }, z.core.$strict>>;
1289
1326
  server: z.ZodDiscriminatedUnion<[z.ZodObject<{
1290
1327
  allowed_origins: z.ZodOptional<z.ZodArray<z.ZodString>>;
1291
1328
  auth: z.ZodObject<{
1329
+ agent_registration: z.ZodOptional<z.ZodEnum<{
1330
+ token: "token";
1331
+ disabled: "disabled";
1332
+ open: "open";
1333
+ }>>;
1292
1334
  client: z.ZodOptional<z.ZodObject<{
1293
1335
  static_token: z.ZodOptional<z.ZodBoolean>;
1294
1336
  token_env: z.ZodOptional<z.ZodString>;
@@ -1300,6 +1342,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1300
1342
  open: "open";
1301
1343
  bearer: "bearer";
1302
1344
  }>;
1345
+ public_read: z.ZodOptional<z.ZodBoolean>;
1303
1346
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
1304
1347
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
1305
1348
  id: z.ZodString;
@@ -1313,6 +1356,13 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1313
1356
  }>>;
1314
1357
  }, z.core.$strict>>>;
1315
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>>;
1365
+ debug_events: z.ZodOptional<z.ZodBoolean>;
1316
1366
  direct_messages: z.ZodOptional<z.ZodBoolean>;
1317
1367
  human_ingress: z.ZodOptional<z.ZodBoolean>;
1318
1368
  listen: z.ZodObject<{
@@ -1359,6 +1409,11 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1359
1409
  url: z.ZodOptional<z.ZodString>;
1360
1410
  }, z.core.$strict>, z.ZodObject<{
1361
1411
  auth: z.ZodObject<{
1412
+ agent_registration: z.ZodOptional<z.ZodEnum<{
1413
+ token: "token";
1414
+ disabled: "disabled";
1415
+ open: "open";
1416
+ }>>;
1362
1417
  client: z.ZodOptional<z.ZodObject<{
1363
1418
  static_token: z.ZodOptional<z.ZodBoolean>;
1364
1419
  token_env: z.ZodOptional<z.ZodString>;
@@ -1370,6 +1425,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1370
1425
  open: "open";
1371
1426
  bearer: "bearer";
1372
1427
  }>;
1428
+ public_read: z.ZodOptional<z.ZodBoolean>;
1373
1429
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
1374
1430
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
1375
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,13 @@ 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>>;
102
+ debug_events: z.ZodOptional<z.ZodBoolean>;
84
103
  direct_messages: z.ZodOptional<z.ZodBoolean>;
85
104
  human_ingress: z.ZodOptional<z.ZodBoolean>;
86
105
  listen: z.ZodObject<{
@@ -127,6 +146,11 @@ declare const teamNetworkServerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
127
146
  url: z.ZodOptional<z.ZodString>;
128
147
  }, z.core.$strict>, z.ZodObject<{
129
148
  auth: z.ZodObject<{
149
+ agent_registration: z.ZodOptional<z.ZodEnum<{
150
+ token: "token";
151
+ disabled: "disabled";
152
+ open: "open";
153
+ }>>;
130
154
  client: z.ZodOptional<z.ZodObject<{
131
155
  static_token: z.ZodOptional<z.ZodBoolean>;
132
156
  token_env: z.ZodOptional<z.ZodString>;
@@ -138,6 +162,7 @@ declare const teamNetworkServerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
138
162
  open: "open";
139
163
  bearer: "bearer";
140
164
  }>;
165
+ public_read: z.ZodOptional<z.ZodBoolean>;
141
166
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
142
167
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
143
168
  id: z.ZodString;
@@ -158,6 +183,15 @@ declare const teamNetworkRoomSchema: z.ZodObject<{
158
183
  id: z.ZodString;
159
184
  members: z.ZodArray<z.ZodString>;
160
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
+ }>>;
161
195
  }, z.core.$strict>;
162
196
  export declare const teamNetworkSchema: z.ZodObject<{
163
197
  id: z.ZodString;
@@ -167,10 +201,24 @@ export declare const teamNetworkSchema: z.ZodObject<{
167
201
  id: z.ZodString;
168
202
  members: z.ZodArray<z.ZodString>;
169
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
+ }>>;
170
213
  }, z.core.$strict>>;
171
214
  server: z.ZodDiscriminatedUnion<[z.ZodObject<{
172
215
  allowed_origins: z.ZodOptional<z.ZodArray<z.ZodString>>;
173
216
  auth: z.ZodObject<{
217
+ agent_registration: z.ZodOptional<z.ZodEnum<{
218
+ token: "token";
219
+ disabled: "disabled";
220
+ open: "open";
221
+ }>>;
174
222
  client: z.ZodOptional<z.ZodObject<{
175
223
  static_token: z.ZodOptional<z.ZodBoolean>;
176
224
  token_env: z.ZodOptional<z.ZodString>;
@@ -182,6 +230,7 @@ export declare const teamNetworkSchema: z.ZodObject<{
182
230
  open: "open";
183
231
  bearer: "bearer";
184
232
  }>;
233
+ public_read: z.ZodOptional<z.ZodBoolean>;
185
234
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
186
235
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
187
236
  id: z.ZodString;
@@ -195,6 +244,13 @@ export declare const teamNetworkSchema: z.ZodObject<{
195
244
  }>>;
196
245
  }, z.core.$strict>>>;
197
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>>;
253
+ debug_events: z.ZodOptional<z.ZodBoolean>;
198
254
  direct_messages: z.ZodOptional<z.ZodBoolean>;
199
255
  human_ingress: z.ZodOptional<z.ZodBoolean>;
200
256
  listen: z.ZodObject<{
@@ -241,6 +297,11 @@ export declare const teamNetworkSchema: z.ZodObject<{
241
297
  url: z.ZodOptional<z.ZodString>;
242
298
  }, z.core.$strict>, z.ZodObject<{
243
299
  auth: z.ZodObject<{
300
+ agent_registration: z.ZodOptional<z.ZodEnum<{
301
+ token: "token";
302
+ disabled: "disabled";
303
+ open: "open";
304
+ }>>;
244
305
  client: z.ZodOptional<z.ZodObject<{
245
306
  static_token: z.ZodOptional<z.ZodBoolean>;
246
307
  token_env: z.ZodOptional<z.ZodString>;
@@ -252,6 +313,7 @@ export declare const teamNetworkSchema: z.ZodObject<{
252
313
  open: "open";
253
314
  bearer: "bearer";
254
315
  }>;
316
+ public_read: z.ZodOptional<z.ZodBoolean>;
255
317
  tokens: z.ZodOptional<z.ZodArray<z.ZodObject<{
256
318
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
257
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,8 @@ 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(),
193
+ debug_events: z.boolean().optional(),
185
194
  direct_messages: z.boolean().optional(),
186
195
  human_ingress: z.boolean().optional(),
187
196
  listen: teamNetworkListenSchema,
@@ -200,13 +209,13 @@ const teamNetworkManagedServerSchema = z
200
209
  message: "managed server with auth.mode bearer requires at least one auth token"
201
210
  });
202
211
  }
203
- if (!value.auth.client?.token_id) {
212
+ if (value.auth.client && !value.auth.client.token_id) {
204
213
  context.addIssue({
205
214
  code: z.ZodIssueCode.custom,
206
215
  message: "managed bearer auth requires auth.client.token_id"
207
216
  });
208
217
  }
209
- 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)) {
210
219
  context.addIssue({
211
220
  code: z.ZodIssueCode.custom,
212
221
  message: "managed bearer auth requires auth.client.token_id"
@@ -242,6 +251,9 @@ const teamNetworkManagedServerSchema = z
242
251
  message: "managed open auth client token source must be token_id"
243
252
  });
244
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
+ }
245
257
  }
246
258
  });
247
259
  const teamNetworkExternalServerSchema = z
@@ -286,7 +298,7 @@ const teamNetworkExternalServerSchema = z
286
298
  message: "external open auth requires token_env or token_path"
287
299
  });
288
300
  }
289
- if (value.auth.mode === "bearer") {
301
+ if (value.auth.mode === "bearer" && value.auth.client) {
290
302
  const sourceCount = countTruthy([
291
303
  value.auth.client.token_id,
292
304
  value.auth.client.token_env,
@@ -307,8 +319,10 @@ const teamNetworkServerSchema = z.discriminatedUnion("mode", [
307
319
  const teamNetworkRoomSchema = z
308
320
  .object({
309
321
  id: z.string().trim().min(1),
310
- members: z.array(z.string().trim().min(1)).min(1),
311
- 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()
312
326
  })
313
327
  .strict();
314
328
  export const teamNetworkSchema = z
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spawnfile",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Canonical source compiler for autonomous agents and teams.",
5
5
  "license": "MIT",
6
6
  "type": "module",