agent-relay 4.0.2 → 4.0.3

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.
Files changed (175) hide show
  1. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  2. package/bin/agent-relay-broker-darwin-x64 +0 -0
  3. package/bin/agent-relay-broker-linux-arm64 +0 -0
  4. package/bin/agent-relay-broker-linux-x64 +0 -0
  5. package/dist/index.cjs +7906 -2084
  6. package/dist/packages/sdk/src/provisioner/seeder.d.ts +17 -0
  7. package/dist/packages/sdk/src/provisioner/seeder.d.ts.map +1 -0
  8. package/dist/packages/sdk/src/provisioner/seeder.js +419 -0
  9. package/dist/packages/sdk/src/provisioner/seeder.js.map +1 -0
  10. package/dist/packages/sdk/src/provisioner/token.d.ts +38 -0
  11. package/dist/packages/sdk/src/provisioner/token.d.ts.map +1 -0
  12. package/dist/packages/sdk/src/provisioner/token.js +74 -0
  13. package/dist/packages/sdk/src/provisioner/token.js.map +1 -0
  14. package/dist/src/cli/commands/core.d.ts.map +1 -1
  15. package/dist/src/cli/commands/core.js +7 -3
  16. package/dist/src/cli/commands/core.js.map +1 -1
  17. package/dist/src/cli/commands/on/provision.d.ts.map +1 -1
  18. package/dist/src/cli/commands/on/provision.js +8 -3
  19. package/dist/src/cli/commands/on/provision.js.map +1 -1
  20. package/dist/src/cli/commands/on/start.d.ts +3 -0
  21. package/dist/src/cli/commands/on/start.d.ts.map +1 -1
  22. package/dist/src/cli/commands/on/start.js +113 -84
  23. package/dist/src/cli/commands/on/start.js.map +1 -1
  24. package/dist/src/cli/commands/on/symlink-mount.d.ts +12 -0
  25. package/dist/src/cli/commands/on/symlink-mount.d.ts.map +1 -0
  26. package/dist/src/cli/commands/on/symlink-mount.js +304 -0
  27. package/dist/src/cli/commands/on/symlink-mount.js.map +1 -0
  28. package/dist/src/cli/commands/on.d.ts.map +1 -1
  29. package/dist/src/cli/commands/on.js +3 -0
  30. package/dist/src/cli/commands/on.js.map +1 -1
  31. package/install.sh +4 -0
  32. package/package.json +9 -9
  33. package/packages/acp-bridge/package.json +2 -2
  34. package/packages/brand/package.json +1 -1
  35. package/packages/cloud/package.json +2 -2
  36. package/packages/config/package.json +1 -1
  37. package/packages/hooks/package.json +4 -4
  38. package/packages/memory/package.json +2 -2
  39. package/packages/openclaw/package.json +2 -2
  40. package/packages/policy/package.json +2 -2
  41. package/packages/sdk/dist/client.d.ts +3 -10
  42. package/packages/sdk/dist/client.d.ts.map +1 -1
  43. package/packages/sdk/dist/client.js +2 -0
  44. package/packages/sdk/dist/client.js.map +1 -1
  45. package/packages/sdk/dist/provisioner/__tests__/audit.test.d.ts +2 -0
  46. package/packages/sdk/dist/provisioner/__tests__/audit.test.d.ts.map +1 -0
  47. package/packages/sdk/dist/provisioner/__tests__/audit.test.js +45 -0
  48. package/packages/sdk/dist/provisioner/__tests__/audit.test.js.map +1 -0
  49. package/packages/sdk/dist/provisioner/__tests__/compiler.test.d.ts +2 -0
  50. package/packages/sdk/dist/provisioner/__tests__/compiler.test.d.ts.map +1 -0
  51. package/packages/sdk/dist/provisioner/__tests__/compiler.test.js +345 -0
  52. package/packages/sdk/dist/provisioner/__tests__/compiler.test.js.map +1 -0
  53. package/packages/sdk/dist/provisioner/__tests__/presets.test.d.ts +2 -0
  54. package/packages/sdk/dist/provisioner/__tests__/presets.test.d.ts.map +1 -0
  55. package/packages/sdk/dist/provisioner/__tests__/presets.test.js +23 -0
  56. package/packages/sdk/dist/provisioner/__tests__/presets.test.js.map +1 -0
  57. package/packages/sdk/dist/provisioner/__tests__/seeder.test.d.ts +2 -0
  58. package/packages/sdk/dist/provisioner/__tests__/seeder.test.d.ts.map +1 -0
  59. package/packages/sdk/dist/provisioner/__tests__/seeder.test.js +224 -0
  60. package/packages/sdk/dist/provisioner/__tests__/seeder.test.js.map +1 -0
  61. package/packages/sdk/dist/provisioner/__tests__/tar-seeder.test.d.ts +2 -0
  62. package/packages/sdk/dist/provisioner/__tests__/tar-seeder.test.d.ts.map +1 -0
  63. package/packages/sdk/dist/provisioner/__tests__/tar-seeder.test.js +191 -0
  64. package/packages/sdk/dist/provisioner/__tests__/tar-seeder.test.js.map +1 -0
  65. package/packages/sdk/dist/provisioner/__tests__/token-factory.test.d.ts +2 -0
  66. package/packages/sdk/dist/provisioner/__tests__/token-factory.test.d.ts.map +1 -0
  67. package/packages/sdk/dist/provisioner/__tests__/token-factory.test.js +127 -0
  68. package/packages/sdk/dist/provisioner/__tests__/token-factory.test.js.map +1 -0
  69. package/packages/sdk/dist/provisioner/__tests__/token.test.d.ts +2 -0
  70. package/packages/sdk/dist/provisioner/__tests__/token.test.d.ts.map +1 -0
  71. package/packages/sdk/dist/provisioner/__tests__/token.test.js +44 -0
  72. package/packages/sdk/dist/provisioner/__tests__/token.test.js.map +1 -0
  73. package/packages/sdk/dist/provisioner/audit.d.ts +19 -0
  74. package/packages/sdk/dist/provisioner/audit.d.ts.map +1 -0
  75. package/packages/sdk/dist/provisioner/audit.js +74 -0
  76. package/packages/sdk/dist/provisioner/audit.js.map +1 -0
  77. package/packages/sdk/dist/provisioner/compiler.d.ts +23 -0
  78. package/packages/sdk/dist/provisioner/compiler.d.ts.map +1 -0
  79. package/packages/sdk/dist/provisioner/compiler.js +355 -0
  80. package/packages/sdk/dist/provisioner/compiler.js.map +1 -0
  81. package/packages/sdk/dist/provisioner/index.d.ts +9 -0
  82. package/packages/sdk/dist/provisioner/index.d.ts.map +1 -0
  83. package/packages/sdk/dist/provisioner/index.js +266 -0
  84. package/packages/sdk/dist/provisioner/index.js.map +1 -0
  85. package/packages/sdk/dist/provisioner/mount.d.ts +14 -0
  86. package/packages/sdk/dist/provisioner/mount.d.ts.map +1 -0
  87. package/packages/sdk/dist/provisioner/mount.js +329 -0
  88. package/packages/sdk/dist/provisioner/mount.js.map +1 -0
  89. package/packages/sdk/dist/provisioner/seeder.d.ts +17 -0
  90. package/packages/sdk/dist/provisioner/seeder.d.ts.map +1 -0
  91. package/packages/sdk/dist/provisioner/seeder.js +419 -0
  92. package/packages/sdk/dist/provisioner/seeder.js.map +1 -0
  93. package/packages/sdk/dist/provisioner/token.d.ts +38 -0
  94. package/packages/sdk/dist/provisioner/token.d.ts.map +1 -0
  95. package/packages/sdk/dist/provisioner/token.js +74 -0
  96. package/packages/sdk/dist/provisioner/token.js.map +1 -0
  97. package/packages/sdk/dist/provisioner/types.d.ts +133 -0
  98. package/packages/sdk/dist/provisioner/types.d.ts.map +1 -0
  99. package/packages/sdk/dist/provisioner/types.js +2 -0
  100. package/packages/sdk/dist/provisioner/types.js.map +1 -0
  101. package/packages/sdk/dist/relay.d.ts +6 -0
  102. package/packages/sdk/dist/relay.d.ts.map +1 -1
  103. package/packages/sdk/dist/relay.js +17 -5
  104. package/packages/sdk/dist/relay.js.map +1 -1
  105. package/packages/sdk/dist/types.d.ts +9 -0
  106. package/packages/sdk/dist/types.d.ts.map +1 -1
  107. package/packages/sdk/dist/workflows/__tests__/e2e-permissions.test.d.ts +2 -0
  108. package/packages/sdk/dist/workflows/__tests__/e2e-permissions.test.d.ts.map +1 -0
  109. package/packages/sdk/dist/workflows/__tests__/e2e-permissions.test.js +331 -0
  110. package/packages/sdk/dist/workflows/__tests__/e2e-permissions.test.js.map +1 -0
  111. package/packages/sdk/dist/workflows/__tests__/permission-types.test.d.ts +2 -0
  112. package/packages/sdk/dist/workflows/__tests__/permission-types.test.d.ts.map +1 -0
  113. package/packages/sdk/dist/workflows/__tests__/permission-types.test.js +124 -0
  114. package/packages/sdk/dist/workflows/__tests__/permission-types.test.js.map +1 -0
  115. package/packages/sdk/dist/workflows/__tests__/permissions-integration.test.d.ts +2 -0
  116. package/packages/sdk/dist/workflows/__tests__/permissions-integration.test.d.ts.map +1 -0
  117. package/packages/sdk/dist/workflows/__tests__/permissions-integration.test.js +526 -0
  118. package/packages/sdk/dist/workflows/__tests__/permissions-integration.test.js.map +1 -0
  119. package/packages/sdk/dist/workflows/dry-run-format.d.ts.map +1 -1
  120. package/packages/sdk/dist/workflows/dry-run-format.js +8 -0
  121. package/packages/sdk/dist/workflows/dry-run-format.js.map +1 -1
  122. package/packages/sdk/dist/workflows/runner.d.ts +14 -0
  123. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  124. package/packages/sdk/dist/workflows/runner.js +455 -6
  125. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  126. package/packages/sdk/dist/workflows/types.d.ts +190 -0
  127. package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
  128. package/packages/sdk/dist/workflows/types.js +29 -0
  129. package/packages/sdk/dist/workflows/types.js.map +1 -1
  130. package/packages/sdk/package.json +6 -2
  131. package/packages/sdk/src/__tests__/orchestration-upgrades.test.ts +123 -1
  132. package/packages/sdk/src/__tests__/provisioner-mount.test.ts +126 -0
  133. package/packages/sdk/src/__tests__/spawn-token.test.ts +41 -0
  134. package/packages/sdk/src/__tests__/workflow-runner.test.ts +77 -45
  135. package/packages/sdk/src/client.ts +4 -8
  136. package/packages/sdk/src/provisioner/__tests__/audit.test.ts +62 -0
  137. package/packages/sdk/src/provisioner/__tests__/compiler.test.ts +369 -0
  138. package/packages/sdk/src/provisioner/__tests__/presets.test.ts +25 -0
  139. package/packages/sdk/src/provisioner/__tests__/seeder.test.ts +284 -0
  140. package/packages/sdk/src/provisioner/__tests__/tar-seeder.test.ts +249 -0
  141. package/packages/sdk/src/provisioner/__tests__/token-factory.test.ts +172 -0
  142. package/packages/sdk/src/provisioner/__tests__/token.test.ts +53 -0
  143. package/packages/sdk/src/provisioner/audit.ts +104 -0
  144. package/packages/sdk/src/provisioner/compiler.ts +498 -0
  145. package/packages/sdk/src/provisioner/index.ts +332 -0
  146. package/packages/sdk/src/provisioner/mount.ts +419 -0
  147. package/packages/sdk/src/provisioner/seeder.ts +571 -0
  148. package/packages/sdk/src/provisioner/token.ts +112 -0
  149. package/packages/sdk/src/provisioner/types.ts +188 -0
  150. package/packages/sdk/src/relay.ts +31 -9
  151. package/packages/sdk/src/types.ts +9 -0
  152. package/packages/sdk/src/workflows/__tests__/e2e-permissions.test.ts +407 -0
  153. package/packages/sdk/src/workflows/__tests__/fixtures/.agentignore +2 -0
  154. package/packages/sdk/src/workflows/__tests__/fixtures/.reader.agentreadonly +2 -0
  155. package/packages/sdk/src/workflows/__tests__/fixtures/permission-test.yaml +42 -0
  156. package/packages/sdk/src/workflows/__tests__/permission-types.test.ts +154 -0
  157. package/packages/sdk/src/workflows/__tests__/permissions-integration.test.ts +649 -0
  158. package/packages/sdk/src/workflows/builtin-templates/bug-fix.yaml +13 -9
  159. package/packages/sdk/src/workflows/builtin-templates/code-review.yaml +12 -8
  160. package/packages/sdk/src/workflows/builtin-templates/competitive.yaml +11 -7
  161. package/packages/sdk/src/workflows/builtin-templates/documentation.yaml +16 -8
  162. package/packages/sdk/src/workflows/builtin-templates/feature-dev.yaml +13 -9
  163. package/packages/sdk/src/workflows/builtin-templates/refactor.yaml +13 -9
  164. package/packages/sdk/src/workflows/builtin-templates/review-loop.yaml +14 -10
  165. package/packages/sdk/src/workflows/builtin-templates/security-audit.yaml +19 -9
  166. package/packages/sdk/src/workflows/dry-run-format.ts +14 -1
  167. package/packages/sdk/src/workflows/runner.ts +559 -6
  168. package/packages/sdk/src/workflows/schema.json +204 -114
  169. package/packages/sdk/src/workflows/types.ts +266 -1
  170. package/packages/sdk/vitest.config.ts +5 -1
  171. package/packages/sdk-py/pyproject.toml +1 -1
  172. package/packages/telemetry/package.json +1 -1
  173. package/packages/trajectory/package.json +2 -2
  174. package/packages/user-directory/package.json +2 -2
  175. package/packages/utils/package.json +2 -2
@@ -0,0 +1,332 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { getDefaultPermissionAuditPath, PermissionAuditLog } from './audit.js';
5
+ import { compileAgentScopes } from './compiler.js';
6
+ import { ensureRelayfileMount } from './mount.js';
7
+ import { createWorkspaceIfNeeded, seedWorkspace, seedWorkflowAcls } from './seeder.js';
8
+ import { DEFAULT_ADMIN_AGENT_NAME, DEFAULT_ADMIN_SCOPES, mintAgentToken } from './token.js';
9
+ import type {
10
+ AgentPermissions,
11
+ AgentProvisionMap,
12
+ AgentProvisionResult,
13
+ CompiledAgentPermissions,
14
+ ProvisionResult,
15
+ ProvisionSummary,
16
+ WorkflowProvisionConfig,
17
+ } from './types.js';
18
+
19
+ export * from './compiler.js';
20
+ export * from './mount.js';
21
+ export * from './seeder.js';
22
+ export * from './token.js';
23
+ export * from './types.js';
24
+ export * from './audit.js';
25
+
26
+ interface ProvisionableAgent {
27
+ name: string;
28
+ permissions: AgentPermissions;
29
+ resolutionSource: 'configured' | 'auto-discovered';
30
+ }
31
+
32
+ const DEFAULT_AGENT_NAME = 'default-agent';
33
+
34
+ function discoverAgentNames(projectDir: string): string[] {
35
+ if (!existsSync(projectDir)) {
36
+ return [DEFAULT_AGENT_NAME];
37
+ }
38
+
39
+ const agentNames = new Set<string>();
40
+
41
+ for (const entry of readdirSync(projectDir)) {
42
+ const match = entry.match(/^\.(.+)\.(agentignore|agentreadonly)$/u);
43
+ if (match?.[1]) {
44
+ agentNames.add(match[1]);
45
+ }
46
+ }
47
+
48
+ const discovered = [...agentNames].sort((left, right) => left.localeCompare(right));
49
+ return discovered.length > 0 ? discovered : [DEFAULT_AGENT_NAME];
50
+ }
51
+
52
+ function resolveAgents(config: WorkflowProvisionConfig): ProvisionableAgent[] {
53
+ const configuredAgents = Object.entries(config.agents ?? {});
54
+ if (configuredAgents.length > 0) {
55
+ return configuredAgents.map(([name, permissions]) => ({
56
+ name,
57
+ permissions: permissions ?? {},
58
+ resolutionSource: 'configured',
59
+ }));
60
+ }
61
+
62
+ return discoverAgentNames(config.projectDir).map((name) => ({
63
+ name,
64
+ permissions: {},
65
+ resolutionSource: 'auto-discovered',
66
+ }));
67
+ }
68
+
69
+ function buildSummary(compilations: readonly CompiledAgentPermissions[]): ProvisionSummary {
70
+ return compilations.reduce<ProvisionSummary>(
71
+ (summary, compiled) => ({
72
+ readonly: summary.readonly + compiled.summary.readonly,
73
+ readwrite: summary.readwrite + compiled.summary.readwrite,
74
+ denied: summary.denied + compiled.summary.denied,
75
+ customScopes: summary.customScopes + compiled.summary.customScopes,
76
+ }),
77
+ {
78
+ readonly: 0,
79
+ readwrite: 0,
80
+ denied: 0,
81
+ customScopes: 0,
82
+ }
83
+ );
84
+ }
85
+
86
+ function buildAgentResult(
87
+ projectDir: string,
88
+ name: string,
89
+ token: string,
90
+ compiled: CompiledAgentPermissions,
91
+ mountPoint?: string
92
+ ): AgentProvisionResult {
93
+ return {
94
+ name,
95
+ tokenPath: path.resolve(projectDir, '.relay', 'tokens', `${name}.jwt`),
96
+ token,
97
+ scopes: [...compiled.scopes],
98
+ compiled,
99
+ mountPoint,
100
+ };
101
+ }
102
+
103
+ function sanitizePathComponent(value: string): string {
104
+ return value.replace(/[^a-zA-Z0-9._-]+/g, '-');
105
+ }
106
+
107
+ function countAclDirectories(compilations: readonly CompiledAgentPermissions[]): number {
108
+ const directories = new Set<string>();
109
+
110
+ for (const compilation of compilations) {
111
+ for (const directory of Object.keys(compilation.acl)) {
112
+ directories.add(directory);
113
+ }
114
+ }
115
+
116
+ return directories.size;
117
+ }
118
+
119
+ export async function provisionWorkflowAgents(config: WorkflowProvisionConfig): Promise<ProvisionResult> {
120
+ const audit = new PermissionAuditLog();
121
+ const auditPath = getDefaultPermissionAuditPath(config.projectDir);
122
+
123
+ try {
124
+ const agents = resolveAgents(config);
125
+ const tokens = new Map<string, string>();
126
+ const scopes = new Map<string, string[]>();
127
+ const mounts = new Map<string, Awaited<ReturnType<typeof ensureRelayfileMount>>>();
128
+ const agentResults: AgentProvisionMap = {};
129
+ const compilations: CompiledAgentPermissions[] = [];
130
+ const compiledByAgent = new Map<string, CompiledAgentPermissions>();
131
+
132
+ for (const agent of agents) {
133
+ audit.log({
134
+ agentName: agent.name,
135
+ action: 'resolve',
136
+ details: {
137
+ source: agent.resolutionSource,
138
+ workspace: config.workspace,
139
+ permissionKeys: Object.keys(agent.permissions).sort(),
140
+ },
141
+ });
142
+
143
+ const compiled = compileAgentScopes({
144
+ agentName: agent.name,
145
+ workspace: config.workspace,
146
+ projectDir: config.projectDir,
147
+ permissions: agent.permissions,
148
+ });
149
+ const token = mintAgentToken({
150
+ secret: config.secret,
151
+ agentName: agent.name,
152
+ workspace: config.workspace,
153
+ scopes: compiled.scopes,
154
+ ttlSeconds: config.tokenTtlSeconds,
155
+ });
156
+
157
+ audit.log({
158
+ agentName: agent.name,
159
+ action: 'mint',
160
+ details: {
161
+ workspace: config.workspace,
162
+ jwtPath: path.resolve(config.projectDir, '.relay', 'tokens', `${agent.name}.jwt`),
163
+ scopeCount: compiled.scopes.length,
164
+ scopes: [...compiled.scopes],
165
+ ttlSeconds: config.tokenTtlSeconds ?? null,
166
+ },
167
+ });
168
+
169
+ tokens.set(agent.name, token);
170
+ scopes.set(agent.name, [...compiled.scopes]);
171
+ compilations.push(compiled);
172
+ compiledByAgent.set(agent.name, compiled);
173
+ }
174
+
175
+ const adminScopes = [...(config.adminScopes ?? DEFAULT_ADMIN_SCOPES)];
176
+ const adminToken = mintAgentToken({
177
+ secret: config.secret,
178
+ agentName: DEFAULT_ADMIN_AGENT_NAME,
179
+ workspace: config.workspace,
180
+ scopes: adminScopes,
181
+ ttlSeconds: config.tokenTtlSeconds,
182
+ });
183
+
184
+ audit.log({
185
+ agentName: DEFAULT_ADMIN_AGENT_NAME,
186
+ action: 'mint',
187
+ details: {
188
+ workspace: config.workspace,
189
+ role: 'admin',
190
+ scopeCount: adminScopes.length,
191
+ scopes: adminScopes,
192
+ ttlSeconds: config.tokenTtlSeconds ?? null,
193
+ },
194
+ });
195
+
196
+ let seededAclCount = 0;
197
+ let seededFileCount = 0;
198
+
199
+ if (!config.skipSeeding) {
200
+ await createWorkspaceIfNeeded(config.relayfileBaseUrl, adminToken, config.workspace);
201
+ audit.log({
202
+ agentName: DEFAULT_ADMIN_AGENT_NAME,
203
+ action: 'seed',
204
+ details: {
205
+ workspace: config.workspace,
206
+ step: 'workspace',
207
+ relayfileBaseUrl: config.relayfileBaseUrl,
208
+ },
209
+ });
210
+
211
+ seededFileCount = await seedWorkspace(
212
+ config.relayfileBaseUrl,
213
+ adminToken,
214
+ config.workspace,
215
+ config.projectDir,
216
+ config.excludeDirs ?? []
217
+ );
218
+ audit.log({
219
+ agentName: DEFAULT_ADMIN_AGENT_NAME,
220
+ action: 'seed',
221
+ details: {
222
+ workspace: config.workspace,
223
+ step: 'files',
224
+ projectDir: config.projectDir,
225
+ excludeDirs: config.excludeDirs ?? [],
226
+ fileCount: seededFileCount,
227
+ },
228
+ });
229
+
230
+ await seedWorkflowAcls({
231
+ relayfileUrl: config.relayfileBaseUrl,
232
+ adminToken,
233
+ workspace: config.workspace,
234
+ agents: compilations.map((compilation) => ({
235
+ name: compilation.agentName,
236
+ acl: compilation.acl,
237
+ })),
238
+ });
239
+ seededAclCount = countAclDirectories(compilations);
240
+ audit.log({
241
+ agentName: DEFAULT_ADMIN_AGENT_NAME,
242
+ action: 'seed',
243
+ details: {
244
+ workspace: config.workspace,
245
+ step: 'acl',
246
+ directoryCount: seededAclCount,
247
+ agentCount: compilations.length,
248
+ },
249
+ });
250
+ }
251
+
252
+ if (!config.skipMount) {
253
+ const mountRoot = path.resolve(config.mountBaseDir ?? path.join(config.projectDir, '.relay'));
254
+ try {
255
+ for (const agent of agents) {
256
+ const token = tokens.get(agent.name);
257
+ const compiled = compiledByAgent.get(agent.name);
258
+ if (!token || !compiled) {
259
+ continue;
260
+ }
261
+
262
+ const mountHandle = await ensureRelayfileMount({
263
+ binaryPath: config.mountBinaryPath,
264
+ relayfileUrl: config.relayfileBaseUrl,
265
+ workspace: config.workspace,
266
+ token,
267
+ mountPoint: path.join(
268
+ mountRoot,
269
+ `workspace-${sanitizePathComponent(config.workspace)}-${sanitizePathComponent(agent.name)}`
270
+ ),
271
+ });
272
+
273
+ mounts.set(agent.name, mountHandle);
274
+ agentResults[agent.name] = buildAgentResult(
275
+ config.projectDir,
276
+ agent.name,
277
+ token,
278
+ compiled,
279
+ mountHandle.mountPoint
280
+ );
281
+ }
282
+ } catch (mountError) {
283
+ for (const [, mount] of mounts) {
284
+ try {
285
+ if (typeof mount.stop === 'function') {
286
+ await mount.stop();
287
+ }
288
+ } catch {
289
+ // Best-effort cleanup — ignore individual stop failures.
290
+ }
291
+ }
292
+ mounts.clear();
293
+ throw mountError;
294
+ }
295
+ } else {
296
+ for (const agent of agents) {
297
+ const token = tokens.get(agent.name);
298
+ const compiled = compiledByAgent.get(agent.name);
299
+ if (!token || !compiled) {
300
+ continue;
301
+ }
302
+
303
+ agentResults[agent.name] = buildAgentResult(config.projectDir, agent.name, token, compiled);
304
+ }
305
+ }
306
+
307
+ return {
308
+ agents: agentResults,
309
+ agentNames: agents.map((agent) => agent.name),
310
+ adminToken,
311
+ seededFileCount,
312
+ seededAclCount,
313
+ summary: buildSummary(compilations),
314
+ mounts,
315
+ tokens,
316
+ scopes,
317
+ };
318
+ } finally {
319
+ try {
320
+ await audit.writeTo(auditPath);
321
+ } catch (error) {
322
+ if (config.verbose) {
323
+ const message = error instanceof Error ? error.message : String(error);
324
+ console.warn(`Failed to write permission audit to ${auditPath}: ${message}`);
325
+ }
326
+ }
327
+
328
+ if (config.verbose) {
329
+ console.info(audit.summary());
330
+ }
331
+ }
332
+ }