@leg3ndy/otto-bridge 0.9.2 → 1.0.1

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.
@@ -0,0 +1,1044 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import process from "node:process";
6
+ import { collectStructuredPatchTargets } from "../patch/structured_patch.js";
7
+ const MAX_INSTRUCTION_CHARS = 12_000;
8
+ const DEFAULT_REPO_MARKERS = [".git"];
9
+ const DEFAULT_REPO_MANIFEST_PATHS = [
10
+ "AGENTS.md",
11
+ "README.md",
12
+ "package.json",
13
+ "pyproject.toml",
14
+ "requirements.txt",
15
+ "Cargo.toml",
16
+ "go.mod",
17
+ ];
18
+ const DEFAULT_REPO_LOCKFILE_PATHS = [
19
+ "package-lock.json",
20
+ "pnpm-lock.yaml",
21
+ "yarn.lock",
22
+ "bun.lockb",
23
+ "poetry.lock",
24
+ "Pipfile.lock",
25
+ "Cargo.lock",
26
+ "go.sum",
27
+ ];
28
+ const DEFAULT_IGNORED_DIRECTORY_NAMES = [
29
+ ".git",
30
+ ".next",
31
+ ".venv",
32
+ "__pycache__",
33
+ "build",
34
+ "coverage",
35
+ "dist",
36
+ "node_modules",
37
+ "venv",
38
+ ];
39
+ const DEFAULT_KEY_FILE_PATTERNS = [
40
+ "AGENTS.md",
41
+ "README.md",
42
+ "package.json",
43
+ "package-lock.json",
44
+ "pnpm-lock.yaml",
45
+ "yarn.lock",
46
+ "bun.lockb",
47
+ "tsconfig.json",
48
+ "pyproject.toml",
49
+ "requirements.txt",
50
+ "poetry.lock",
51
+ "Pipfile.lock",
52
+ "Cargo.toml",
53
+ "Cargo.lock",
54
+ "go.mod",
55
+ "go.sum",
56
+ ];
57
+ const WORKSPACE_INDEX_EXTENSION_LIMIT = 8;
58
+ const WORKSPACE_INDEX_TOP_LEVEL_LIMIT = 16;
59
+ const LOCKFILE_PACKAGE_MANAGER_MAP = {
60
+ "package-lock.json": "npm",
61
+ "pnpm-lock.yaml": "pnpm",
62
+ "yarn.lock": "yarn",
63
+ "bun.lockb": "bun",
64
+ "poetry.lock": "poetry",
65
+ "Pipfile.lock": "pipenv",
66
+ "Cargo.lock": "cargo",
67
+ "go.sum": "go",
68
+ };
69
+ const WORKSPACE_OBSERVE_ONLY_ACTIONS = new Set([
70
+ "filesystem_inspect",
71
+ "list_files",
72
+ "count_files",
73
+ "read_file",
74
+ "git_status",
75
+ "git_diff",
76
+ ]);
77
+ const WORKSPACE_DEV_ASSIST_ACTIONS = new Set([
78
+ ...WORKSPACE_OBSERVE_ONLY_ACTIONS,
79
+ "run_shell",
80
+ "run_tests",
81
+ "git_clone",
82
+ "git_fetch",
83
+ "git_checkout",
84
+ ]);
85
+ const WORKSPACE_CODING_ACTIONS = new Set([
86
+ "write_text_file",
87
+ "write_json_file",
88
+ "apply_patch",
89
+ "mkdir",
90
+ "move_file",
91
+ ]);
92
+ const WORKSPACE_RELEASE_ACTIONS = new Set([
93
+ "trash_path",
94
+ "delete_file",
95
+ "git_add",
96
+ "git_commit",
97
+ "git_push",
98
+ "git_rebase",
99
+ "git_merge",
100
+ "git_tag",
101
+ ]);
102
+ const WORKSPACE_POLICY_ALL_ACTIONS = new Set([
103
+ ...WORKSPACE_DEV_ASSIST_ACTIONS,
104
+ ...WORKSPACE_CODING_ACTIONS,
105
+ ...WORKSPACE_RELEASE_ACTIONS,
106
+ ]);
107
+ const WORKSPACE_POLICY_ALLOWED_ACTIONS = {
108
+ observe_only: new Set([...WORKSPACE_OBSERVE_ONLY_ACTIONS]),
109
+ dev_assist: new Set([...WORKSPACE_DEV_ASSIST_ACTIONS]),
110
+ workspace_coding: new Set([...WORKSPACE_DEV_ASSIST_ACTIONS, ...WORKSPACE_CODING_ACTIONS]),
111
+ release_operator: new Set([...WORKSPACE_POLICY_ALL_ACTIONS]),
112
+ };
113
+ const WORKSPACE_POLICY_CONFIRM_ACTIONS = new Set([
114
+ "git_clone",
115
+ "git_fetch",
116
+ "git_checkout",
117
+ ...WORKSPACE_RELEASE_ACTIONS,
118
+ ]);
119
+ const WORKSPACE_POLICY_TITLES = {
120
+ observe_only: "Observe Only",
121
+ dev_assist: "Dev Assist",
122
+ workspace_coding: "Workspace Coding",
123
+ release_operator: "Release Operator",
124
+ };
125
+ function asString(value) {
126
+ return typeof value === "string" ? value.trim() : "";
127
+ }
128
+ function uniqueStrings(values) {
129
+ return Array.from(new Set(values.map((item) => asString(item)).filter(Boolean)));
130
+ }
131
+ function clampInteger(value, fallback, min, max) {
132
+ const numeric = Number(value);
133
+ if (!Number.isFinite(numeric)) {
134
+ return fallback;
135
+ }
136
+ return Math.min(max, Math.max(min, Math.trunc(numeric)));
137
+ }
138
+ function clipInstructionText(value) {
139
+ if (value.length <= MAX_INSTRUCTION_CHARS) {
140
+ return value;
141
+ }
142
+ return `${value.slice(0, MAX_INSTRUCTION_CHARS)}\n\n[truncado: mostrando ${MAX_INSTRUCTION_CHARS} de ${value.length} caracteres.]`;
143
+ }
144
+ function toRelativePath(basePath, targetPath) {
145
+ const relativePath = path.relative(basePath, targetPath);
146
+ return relativePath || ".";
147
+ }
148
+ function uniqueRuntimeStrings(values, limit) {
149
+ const normalized = values
150
+ .map((item) => asString(item))
151
+ .filter(Boolean);
152
+ return Array.from(new Set(normalized)).slice(0, limit);
153
+ }
154
+ function normalizeWorkspaceMemory(value, workspaceId) {
155
+ if (!value) {
156
+ return undefined;
157
+ }
158
+ const jobCount = Number.isFinite(Number(value.job_count)) ? Number(value.job_count) : 0;
159
+ const summary = asString(value.summary)
160
+ || `Memoria curta do workspace ${workspaceId} com ${jobCount} job${jobCount === 1 ? "" : "s"} recente${jobCount === 1 ? "" : "s"}.`;
161
+ return {
162
+ memory_id: asString(value.memory_id) || `workspace_memory.${workspaceId}`,
163
+ workspace_id: asString(value.workspace_id) || workspaceId,
164
+ source: asString(value.source) || "device_job_history",
165
+ job_count: Math.max(0, jobCount),
166
+ recent_job_ids: uniqueRuntimeStrings(value.recent_job_ids || [], 12),
167
+ recent_action_types: uniqueRuntimeStrings(value.recent_action_types || [], 16),
168
+ recent_target_paths: uniqueRuntimeStrings(value.recent_target_paths || [], 16),
169
+ recent_artifact_kinds: uniqueRuntimeStrings(value.recent_artifact_kinds || [], 16),
170
+ latest_job_id: asString(value.latest_job_id) || undefined,
171
+ latest_job_status: asString(value.latest_job_status) || undefined,
172
+ latest_job_at: asString(value.latest_job_at) || undefined,
173
+ instruction_digest: asString(value.instruction_digest) || undefined,
174
+ repo_root: asString(value.repo_root) || undefined,
175
+ scoped_root_path: asString(value.scoped_root_path) || undefined,
176
+ summary,
177
+ status_counts: value.status_counts && typeof value.status_counts === "object" ? value.status_counts : undefined,
178
+ retention: value.retention ? {
179
+ max_job_count: Number.isFinite(Number(value.retention.max_job_count)) ? Number(value.retention.max_job_count) : undefined,
180
+ max_age_days: Number.isFinite(Number(value.retention.max_age_days)) ? Number(value.retention.max_age_days) : undefined,
181
+ filtered_job_count: Number.isFinite(Number(value.retention.filtered_job_count)) ? Number(value.retention.filtered_job_count) : undefined,
182
+ window_started_at: asString(value.retention.window_started_at) || undefined,
183
+ last_cleared_at: asString(value.retention.last_cleared_at) || undefined,
184
+ last_cleared_reason: asString(value.retention.last_cleared_reason) || undefined,
185
+ } : undefined,
186
+ usage_policy: value.usage_policy ? {
187
+ profile_id: asString(value.usage_policy.profile_id) || undefined,
188
+ summary_mode: asString(value.usage_policy.summary_mode) || undefined,
189
+ include_action_types: typeof value.usage_policy.include_action_types === "boolean" ? value.usage_policy.include_action_types : undefined,
190
+ include_target_paths: typeof value.usage_policy.include_target_paths === "boolean" ? value.usage_policy.include_target_paths : undefined,
191
+ include_artifact_kinds: typeof value.usage_policy.include_artifact_kinds === "boolean" ? value.usage_policy.include_artifact_kinds : undefined,
192
+ include_failed_jobs: typeof value.usage_policy.include_failed_jobs === "boolean" ? value.usage_policy.include_failed_jobs : undefined,
193
+ resettable: typeof value.usage_policy.resettable === "boolean" ? value.usage_policy.resettable : undefined,
194
+ } : undefined,
195
+ };
196
+ }
197
+ function inferWorkspacePolicyProfile(actionType, isDestructivePatch = false) {
198
+ if (WORKSPACE_RELEASE_ACTIONS.has(actionType) || (actionType === "apply_patch" && isDestructivePatch)) {
199
+ return "release_operator";
200
+ }
201
+ if (WORKSPACE_CODING_ACTIONS.has(actionType)) {
202
+ return "workspace_coding";
203
+ }
204
+ if (WORKSPACE_DEV_ASSIST_ACTIONS.has(actionType)) {
205
+ return "dev_assist";
206
+ }
207
+ return "observe_only";
208
+ }
209
+ function normalizeWorkspacePolicy(value, workspaceId, actionTypes, destructiveActionTypes) {
210
+ const normalizedActionTypes = uniqueRuntimeStrings(actionTypes, 48);
211
+ const explicitProfileId = asString(value?.profile_id).toLowerCase();
212
+ const inferredProfileId = normalizedActionTypes.reduce((current, actionType) => {
213
+ const next = inferWorkspacePolicyProfile(actionType, destructiveActionTypes.has(actionType));
214
+ const currentAllowed = WORKSPACE_POLICY_ALLOWED_ACTIONS[current] || WORKSPACE_POLICY_ALLOWED_ACTIONS.observe_only;
215
+ const nextAllowed = WORKSPACE_POLICY_ALLOWED_ACTIONS[next] || WORKSPACE_POLICY_ALLOWED_ACTIONS.observe_only;
216
+ return nextAllowed.size > currentAllowed.size ? next : current;
217
+ }, "observe_only");
218
+ const profileId = explicitProfileId && WORKSPACE_POLICY_ALLOWED_ACTIONS[explicitProfileId]
219
+ ? explicitProfileId
220
+ : inferredProfileId;
221
+ const allowedActionTypes = uniqueRuntimeStrings(value?.allowed_action_types?.length
222
+ ? value.allowed_action_types
223
+ : Array.from(WORKSPACE_POLICY_ALLOWED_ACTIONS[profileId] || []), 64);
224
+ const blockedActionTypes = uniqueRuntimeStrings(value?.blocked_action_types?.length
225
+ ? value.blocked_action_types
226
+ : Array.from(WORKSPACE_POLICY_ALL_ACTIONS).filter((item) => !allowedActionTypes.includes(item)), 64);
227
+ const requiresConfirmationActionTypes = uniqueRuntimeStrings(value?.requires_confirmation_action_types?.length
228
+ ? value.requires_confirmation_action_types
229
+ : normalizedActionTypes.filter((item) => WORKSPACE_POLICY_CONFIRM_ACTIONS.has(item) || destructiveActionTypes.has(item)), 32);
230
+ if (!profileId && normalizedActionTypes.length === 0) {
231
+ return undefined;
232
+ }
233
+ const allowShell = typeof value?.allow_shell === "boolean"
234
+ ? value.allow_shell
235
+ : !["observe_only"].includes(profileId);
236
+ const allowCodeWrite = typeof value?.allow_code_write === "boolean"
237
+ ? value.allow_code_write
238
+ : ["workspace_coding", "release_operator"].includes(profileId);
239
+ const allowDestructive = typeof value?.allow_destructive === "boolean"
240
+ ? value.allow_destructive
241
+ : profileId === "release_operator";
242
+ const allowRelease = typeof value?.allow_release === "boolean"
243
+ ? value.allow_release
244
+ : profileId === "release_operator";
245
+ const title = asString(value?.title) || WORKSPACE_POLICY_TITLES[profileId] || "Workspace Policy";
246
+ const summary = asString(value?.summary)
247
+ || `Policy ${profileId} carregada para o workspace ${workspaceId} com ${allowedActionTypes.length} action types permitidos.`;
248
+ return {
249
+ profile_id: profileId || "observe_only",
250
+ title,
251
+ summary,
252
+ allowed_action_types: allowedActionTypes,
253
+ blocked_action_types: blockedActionTypes,
254
+ requires_confirmation_action_types: requiresConfirmationActionTypes,
255
+ rationale: asString(value?.rationale) || (normalizedActionTypes.length > 0
256
+ ? `Perfil inferido a partir das actions do runtime: ${normalizedActionTypes.join(", ")}.`
257
+ : undefined),
258
+ allow_shell: allowShell,
259
+ allow_code_write: allowCodeWrite,
260
+ allow_destructive: allowDestructive,
261
+ allow_release: allowRelease,
262
+ enforced_by: uniqueRuntimeStrings(value?.enforced_by?.length ? value.enforced_by : ["bridge_workspace_runtime"], 8),
263
+ };
264
+ }
265
+ function isPathInsideRoot(candidatePath, rootPath) {
266
+ const relativePath = path.relative(rootPath, candidatePath);
267
+ return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
268
+ }
269
+ function findBestMatchingRoot(candidatePath, roots) {
270
+ return roots
271
+ .filter((root) => isPathInsideRoot(candidatePath, root.resolved_path))
272
+ .sort((left, right) => right.resolved_path.length - left.resolved_path.length)[0];
273
+ }
274
+ function dirnameForTarget(target, resolvedPath) {
275
+ if (target.kind === "directory" || target.kind === "shell_cwd") {
276
+ return resolvedPath;
277
+ }
278
+ return path.dirname(resolvedPath);
279
+ }
280
+ function looksLikeFilePath(targetPath) {
281
+ const trimmed = targetPath.trim().replace(/[\\/]+$/, "");
282
+ if (!trimmed || trimmed === "~" || trimmed === "." || trimmed === path.sep) {
283
+ return false;
284
+ }
285
+ return path.basename(trimmed).includes(".");
286
+ }
287
+ function inferRuntimeWorkspaceContextFromActions(actions) {
288
+ const roots = [];
289
+ const targets = [];
290
+ const rootKeys = new Set();
291
+ let targetIndex = 1;
292
+ const pushRoot = (rootPath, kind, scopeReason, actionType) => {
293
+ const key = `${kind}:${rootPath}`;
294
+ if (rootKeys.has(key)) {
295
+ const existing = roots.find((item) => `${item.kind || ""}:${item.path}` === key);
296
+ if (existing) {
297
+ existing.action_types = uniqueStrings([...(existing.action_types || []), actionType]);
298
+ }
299
+ return;
300
+ }
301
+ rootKeys.add(key);
302
+ roots.push({
303
+ root_id: `workspace_root_${String(roots.length + 1).padStart(2, "0")}`,
304
+ path: rootPath,
305
+ kind,
306
+ scope_reason: scopeReason,
307
+ action_types: [actionType],
308
+ source: "bridge_action_fallback",
309
+ });
310
+ };
311
+ for (const action of actions) {
312
+ const actionType = asString(action.type).toLowerCase();
313
+ if (!actionType) {
314
+ continue;
315
+ }
316
+ if (actionType === "git_clone") {
317
+ const destinationPath = asString(action.destination_path)
318
+ || asString(action.destination)
319
+ || asString(action.path);
320
+ if (!destinationPath) {
321
+ continue;
322
+ }
323
+ const expandedDestinationPath = expandUserPathLike(destinationPath);
324
+ const parentPath = path.dirname(expandedDestinationPath);
325
+ pushRoot(parentPath, "declared_path", "git_clone_destination_parent", actionType);
326
+ targets.push({
327
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
328
+ path: parentPath,
329
+ kind: "shell_cwd",
330
+ access_mode: "execute",
331
+ action_type: actionType,
332
+ source: "bridge_action_fallback",
333
+ });
334
+ targetIndex += 1;
335
+ targets.push({
336
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
337
+ path: expandedDestinationPath,
338
+ kind: "directory",
339
+ access_mode: "write",
340
+ action_type: actionType,
341
+ source: "bridge_action_fallback",
342
+ });
343
+ targetIndex += 1;
344
+ continue;
345
+ }
346
+ if (actionType === "run_shell" || actionType === "git_status" || actionType === "git_diff" || actionType === "git_add" || actionType === "git_commit" || actionType === "git_push" || actionType === "git_fetch" || actionType === "git_checkout" || actionType === "git_rebase" || actionType === "git_merge" || actionType === "git_tag" || actionType === "run_tests" || actionType === "apply_patch") {
347
+ const cwd = asString(action.cwd) || ".";
348
+ const scopeReason = actionType === "run_shell"
349
+ ? "shell_cwd_scope"
350
+ : actionType === "apply_patch"
351
+ ? "patch_workspace_scope"
352
+ : actionType === "run_tests"
353
+ ? "test_runner_scope"
354
+ : "git_repo_scope";
355
+ pushRoot(cwd, "process_cwd", scopeReason, actionType);
356
+ if (actionType === "apply_patch") {
357
+ const patchTargets = collectStructuredPatchTargets(asString(action.patch) || "");
358
+ if (patchTargets.length > 0) {
359
+ for (const patchTarget of patchTargets) {
360
+ targets.push({
361
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
362
+ path: patchTarget.path,
363
+ kind: "path",
364
+ access_mode: patchTarget.accessMode,
365
+ action_type: actionType,
366
+ source: "bridge_action_fallback",
367
+ });
368
+ targetIndex += 1;
369
+ }
370
+ }
371
+ else {
372
+ targets.push({
373
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
374
+ path: cwd,
375
+ kind: "path",
376
+ access_mode: "write",
377
+ action_type: actionType,
378
+ source: "bridge_action_fallback",
379
+ });
380
+ targetIndex += 1;
381
+ }
382
+ }
383
+ else if (actionType === "git_add") {
384
+ targets.push({
385
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
386
+ path: cwd,
387
+ kind: "shell_cwd",
388
+ access_mode: "execute",
389
+ action_type: actionType,
390
+ source: "bridge_action_fallback",
391
+ });
392
+ targetIndex += 1;
393
+ const rawPaths = Array.isArray(action.paths)
394
+ ? action.paths || []
395
+ : [];
396
+ for (const rawPath of rawPaths) {
397
+ const targetPath = asString(rawPath);
398
+ if (!targetPath) {
399
+ continue;
400
+ }
401
+ targets.push({
402
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
403
+ path: expandUserPathLike(targetPath, expandUserPathLike(cwd)),
404
+ kind: "path",
405
+ access_mode: "write",
406
+ action_type: actionType,
407
+ source: "bridge_action_fallback",
408
+ });
409
+ targetIndex += 1;
410
+ }
411
+ }
412
+ else {
413
+ targets.push({
414
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
415
+ path: cwd,
416
+ kind: "shell_cwd",
417
+ access_mode: "execute",
418
+ action_type: actionType,
419
+ source: "bridge_action_fallback",
420
+ });
421
+ targetIndex += 1;
422
+ }
423
+ continue;
424
+ }
425
+ const targetPath = asString(action.path)
426
+ || (actionType === "move_file"
427
+ ? asString(action.source_path)
428
+ : "");
429
+ if (!targetPath) {
430
+ continue;
431
+ }
432
+ if (actionType === "read_file") {
433
+ pushRoot(path.dirname(targetPath), "declared_path", "file_parent_scope", actionType);
434
+ targets.push({
435
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
436
+ path: targetPath,
437
+ kind: "file",
438
+ access_mode: "read",
439
+ action_type: actionType,
440
+ source: "bridge_action_fallback",
441
+ });
442
+ targetIndex += 1;
443
+ continue;
444
+ }
445
+ if (actionType === "write_text_file") {
446
+ const filename = asString(action.filename) || undefined;
447
+ const rootPath = filename
448
+ ? targetPath
449
+ : (looksLikeFilePath(targetPath) ? path.dirname(targetPath) : targetPath);
450
+ pushRoot(rootPath, "declared_path", "write_scope", actionType);
451
+ targets.push({
452
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
453
+ path: targetPath,
454
+ kind: "file",
455
+ access_mode: "write",
456
+ action_type: actionType,
457
+ source: "bridge_action_fallback",
458
+ filename,
459
+ });
460
+ targetIndex += 1;
461
+ continue;
462
+ }
463
+ if (actionType === "write_json_file") {
464
+ const filename = asString(action.filename) || undefined;
465
+ const rootPath = filename
466
+ ? targetPath
467
+ : (looksLikeFilePath(targetPath) ? path.dirname(targetPath) : targetPath);
468
+ pushRoot(rootPath, "declared_path", "write_scope", actionType);
469
+ targets.push({
470
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
471
+ path: targetPath,
472
+ kind: "file",
473
+ access_mode: "write",
474
+ action_type: actionType,
475
+ source: "bridge_action_fallback",
476
+ filename,
477
+ });
478
+ targetIndex += 1;
479
+ continue;
480
+ }
481
+ if (actionType === "mkdir") {
482
+ pushRoot(path.dirname(targetPath), "declared_path", "directory_target_parent", actionType);
483
+ targets.push({
484
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
485
+ path: targetPath,
486
+ kind: "directory",
487
+ access_mode: "write",
488
+ action_type: actionType,
489
+ source: "bridge_action_fallback",
490
+ });
491
+ targetIndex += 1;
492
+ continue;
493
+ }
494
+ if (actionType === "move_file") {
495
+ const sourcePath = targetPath;
496
+ const destinationPath = asString(action.destination_path)
497
+ || asString(action.destination);
498
+ if (sourcePath) {
499
+ pushRoot(path.dirname(sourcePath), "declared_path", "move_source_parent", actionType);
500
+ }
501
+ if (destinationPath) {
502
+ pushRoot(path.dirname(destinationPath), "declared_path", "move_destination_parent", actionType);
503
+ }
504
+ if (sourcePath) {
505
+ targets.push({
506
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
507
+ path: sourcePath,
508
+ kind: "path",
509
+ access_mode: "write",
510
+ action_type: actionType,
511
+ source: "bridge_action_fallback",
512
+ });
513
+ targetIndex += 1;
514
+ }
515
+ if (destinationPath) {
516
+ targets.push({
517
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
518
+ path: destinationPath,
519
+ kind: "path",
520
+ access_mode: "write",
521
+ action_type: actionType,
522
+ source: "bridge_action_fallback",
523
+ });
524
+ targetIndex += 1;
525
+ }
526
+ continue;
527
+ }
528
+ if (actionType === "trash_path") {
529
+ pushRoot(path.dirname(targetPath), "declared_path", "delete_target_parent", actionType);
530
+ targets.push({
531
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
532
+ path: targetPath,
533
+ kind: "path",
534
+ access_mode: "delete",
535
+ action_type: actionType,
536
+ source: "bridge_action_fallback",
537
+ });
538
+ targetIndex += 1;
539
+ continue;
540
+ }
541
+ if (actionType === "delete_file") {
542
+ pushRoot(path.dirname(targetPath), "declared_path", "delete_target_parent", actionType);
543
+ targets.push({
544
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
545
+ path: targetPath,
546
+ kind: "path",
547
+ access_mode: "delete",
548
+ action_type: actionType,
549
+ source: "bridge_action_fallback",
550
+ });
551
+ targetIndex += 1;
552
+ continue;
553
+ }
554
+ if (actionType === "filesystem_inspect" || actionType === "list_files" || actionType === "count_files") {
555
+ pushRoot(targetPath, "declared_path", "filesystem_scope", actionType);
556
+ targets.push({
557
+ target_id: `workspace_target_${String(targetIndex).padStart(2, "0")}`,
558
+ path: targetPath,
559
+ kind: "directory",
560
+ access_mode: "enumerate",
561
+ action_type: actionType,
562
+ source: "bridge_action_fallback",
563
+ });
564
+ targetIndex += 1;
565
+ }
566
+ }
567
+ if (targets.length === 0) {
568
+ return undefined;
569
+ }
570
+ return {
571
+ workspace_id: "workspace_bridge_fallback",
572
+ roots,
573
+ targets,
574
+ instruction_bundle: {
575
+ resolver: "bridge_local_agents_md",
576
+ entrypoints: ["AGENTS.md"],
577
+ precedence: ["nearest_target_agents_md", "workspace_repo_agents_md", "monorepo_root_agents_md"],
578
+ },
579
+ };
580
+ }
581
+ export function expandUserPathLike(value, baseCwd = process.cwd()) {
582
+ const trimmed = asString(value);
583
+ if (!trimmed) {
584
+ return os.homedir();
585
+ }
586
+ if (trimmed === "~") {
587
+ return os.homedir();
588
+ }
589
+ if (trimmed.startsWith("~/")) {
590
+ return path.join(os.homedir(), trimmed.slice(2));
591
+ }
592
+ if (path.isAbsolute(trimmed)) {
593
+ return trimmed;
594
+ }
595
+ return path.resolve(baseCwd, trimmed);
596
+ }
597
+ async function findRepoRoot(startPath) {
598
+ let current = startPath;
599
+ while (true) {
600
+ try {
601
+ const gitStat = await stat(path.join(current, ".git"));
602
+ if (gitStat.isDirectory() || gitStat.isFile()) {
603
+ return current;
604
+ }
605
+ }
606
+ catch {
607
+ // Keep walking upwards.
608
+ }
609
+ const parent = path.dirname(current);
610
+ if (parent === current) {
611
+ return undefined;
612
+ }
613
+ current = parent;
614
+ }
615
+ }
616
+ function resolveWorkspaceScanBasePath(roots, repoRoot) {
617
+ if (repoRoot) {
618
+ const matchingRoot = findBestMatchingRoot(repoRoot, roots);
619
+ if (matchingRoot) {
620
+ return {
621
+ basePath: repoRoot,
622
+ repoRootWithinWorkspace: true,
623
+ };
624
+ }
625
+ }
626
+ return {
627
+ basePath: roots[0]?.resolved_path || process.cwd(),
628
+ repoRootWithinWorkspace: false,
629
+ };
630
+ }
631
+ async function pathExists(candidatePath) {
632
+ try {
633
+ await stat(candidatePath);
634
+ return true;
635
+ }
636
+ catch {
637
+ return false;
638
+ }
639
+ }
640
+ export async function buildWorkspaceIndex(options) {
641
+ if (options.roots.length === 0) {
642
+ return undefined;
643
+ }
644
+ const descriptor = options.descriptor;
645
+ const { basePath } = resolveWorkspaceScanBasePath(options.roots, options.repoRoot);
646
+ const resolver = asString(descriptor?.resolver) || "bridge_workspace_scan";
647
+ const maxDepth = clampInteger(descriptor?.max_depth, 3, 1, 8);
648
+ const maxDirectories = clampInteger(descriptor?.max_directories, 48, 1, 256);
649
+ const maxFiles = clampInteger(descriptor?.max_files, 160, 1, 2000);
650
+ const ignoredDirectoryNames = new Set(uniqueStrings([
651
+ ...(descriptor?.ignored_directory_names || []),
652
+ ...DEFAULT_IGNORED_DIRECTORY_NAMES,
653
+ ]));
654
+ const keyFilePatterns = new Set(uniqueStrings([
655
+ ...(descriptor?.key_file_patterns || []),
656
+ ...DEFAULT_KEY_FILE_PATTERNS,
657
+ ]));
658
+ const queue = [{ directoryPath: basePath, depth: 0 }];
659
+ const topLevelEntries = [];
660
+ const keyFiles = new Set();
661
+ const extensionCounts = new Map();
662
+ let scannedDirectoryCount = 0;
663
+ let scannedFileCount = 0;
664
+ let truncated = false;
665
+ while (queue.length > 0) {
666
+ if (scannedDirectoryCount >= maxDirectories || scannedFileCount >= maxFiles) {
667
+ truncated = true;
668
+ break;
669
+ }
670
+ const next = queue.shift();
671
+ if (!next) {
672
+ break;
673
+ }
674
+ scannedDirectoryCount += 1;
675
+ let entries;
676
+ try {
677
+ entries = await readdir(next.directoryPath, { withFileTypes: true });
678
+ }
679
+ catch {
680
+ continue;
681
+ }
682
+ entries.sort((left, right) => left.name.localeCompare(right.name));
683
+ for (const entry of entries) {
684
+ const candidatePath = path.join(next.directoryPath, entry.name);
685
+ const relativePath = toRelativePath(basePath, candidatePath);
686
+ if (next.depth === 0 && topLevelEntries.length < WORKSPACE_INDEX_TOP_LEVEL_LIMIT) {
687
+ topLevelEntries.push({
688
+ path: relativePath,
689
+ kind: entry.isDirectory() ? "directory" : "file",
690
+ });
691
+ }
692
+ if (entry.isDirectory()) {
693
+ if (ignoredDirectoryNames.has(entry.name)) {
694
+ continue;
695
+ }
696
+ if (next.depth + 1 <= maxDepth) {
697
+ queue.push({
698
+ directoryPath: candidatePath,
699
+ depth: next.depth + 1,
700
+ });
701
+ }
702
+ else {
703
+ truncated = true;
704
+ }
705
+ continue;
706
+ }
707
+ if (!entry.isFile()) {
708
+ continue;
709
+ }
710
+ scannedFileCount += 1;
711
+ if (keyFilePatterns.has(entry.name)) {
712
+ keyFiles.add(relativePath);
713
+ }
714
+ const extension = path.extname(entry.name).toLowerCase() || "[no_ext]";
715
+ extensionCounts.set(extension, (extensionCounts.get(extension) || 0) + 1);
716
+ if (scannedFileCount >= maxFiles) {
717
+ truncated = true;
718
+ break;
719
+ }
720
+ }
721
+ }
722
+ const dominantExtensions = Array.from(extensionCounts.entries())
723
+ .sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
724
+ .slice(0, WORKSPACE_INDEX_EXTENSION_LIMIT)
725
+ .map(([extension, count]) => ({ extension, count }));
726
+ return {
727
+ resolver,
728
+ workspace_id: options.workspaceId,
729
+ base_path: basePath,
730
+ scanned_directory_count: scannedDirectoryCount,
731
+ scanned_file_count: scannedFileCount,
732
+ truncated,
733
+ top_level_entries: topLevelEntries,
734
+ key_files: Array.from(keyFiles).sort((left, right) => left.localeCompare(right)),
735
+ dominant_extensions: dominantExtensions,
736
+ summary: `Indexei ${scannedFileCount} arquivo${scannedFileCount === 1 ? "" : "s"} e ${scannedDirectoryCount} diretorio${scannedDirectoryCount === 1 ? "" : "s"} em ${path.basename(basePath) || basePath}.`,
737
+ };
738
+ }
739
+ export async function buildRepoManifest(options) {
740
+ if (options.roots.length === 0) {
741
+ return undefined;
742
+ }
743
+ const descriptor = options.descriptor;
744
+ const { basePath, repoRootWithinWorkspace } = resolveWorkspaceScanBasePath(options.roots, options.repoRoot);
745
+ const resolver = asString(descriptor?.resolver) || "bridge_workspace_repo_probe";
746
+ const markerPaths = uniqueStrings([...(descriptor?.marker_paths || []), ...DEFAULT_REPO_MARKERS]);
747
+ const manifestPaths = uniqueStrings([...(descriptor?.manifest_paths || []), ...DEFAULT_REPO_MANIFEST_PATHS]);
748
+ const lockfilePaths = uniqueStrings([...(descriptor?.lockfile_paths || []), ...DEFAULT_REPO_LOCKFILE_PATHS]);
749
+ const includeInstructionSources = descriptor?.include_instruction_sources !== false;
750
+ const detectedMarkers = [];
751
+ const manifestFiles = [];
752
+ const lockfiles = [];
753
+ for (const relativePath of markerPaths) {
754
+ if (await pathExists(path.join(basePath, relativePath))) {
755
+ detectedMarkers.push(relativePath);
756
+ }
757
+ }
758
+ for (const relativePath of manifestPaths) {
759
+ if (await pathExists(path.join(basePath, relativePath))) {
760
+ manifestFiles.push(relativePath);
761
+ }
762
+ }
763
+ for (const relativePath of lockfilePaths) {
764
+ if (await pathExists(path.join(basePath, relativePath))) {
765
+ lockfiles.push(relativePath);
766
+ }
767
+ }
768
+ const packageManagers = uniqueStrings(lockfiles.map((item) => LOCKFILE_PACKAGE_MANAGER_MAP[item] || ""))
769
+ .sort((left, right) => left.localeCompare(right));
770
+ const instructionSourcePaths = includeInstructionSources
771
+ ? uniqueStrings(options.instructionBundle?.sources.map((item) => toRelativePath(basePath, item.path)) || [])
772
+ .sort((left, right) => left.localeCompare(right))
773
+ : [];
774
+ const targetPaths = uniqueStrings(options.targets.map((target) => toRelativePath(basePath, target.resolved_path)))
775
+ .sort((left, right) => left.localeCompare(right));
776
+ const vcs = asString(descriptor?.vcs_hint) || (options.repoRoot ? "git" : "filesystem");
777
+ return {
778
+ resolver,
779
+ workspace_id: options.workspaceId,
780
+ scoped_root_path: basePath,
781
+ detected_repo_root: options.repoRoot || undefined,
782
+ repo_root_within_workspace: repoRootWithinWorkspace,
783
+ repo_name: path.basename(basePath) || basePath,
784
+ vcs,
785
+ detected_markers: detectedMarkers,
786
+ manifest_files: manifestFiles,
787
+ lockfiles,
788
+ package_managers: packageManagers,
789
+ instruction_source_paths: instructionSourcePaths,
790
+ target_paths: targetPaths,
791
+ summary: `Repo manifest para ${path.basename(basePath) || basePath} com ${manifestFiles.length} manifest${manifestFiles.length === 1 ? "" : "s"} e ${lockfiles.length} lockfile${lockfiles.length === 1 ? "" : "s"}.`,
792
+ };
793
+ }
794
+ export function buildWorkspaceMemory(options) {
795
+ const workspaceId = asString(options.workspaceId) || options.workspace?.workspaceId || "workspace_local_runtime";
796
+ const workspace = options.workspace || undefined;
797
+ const priorMemory = options.priorMemory;
798
+ const currentActionTypes = uniqueRuntimeStrings((options.actions || []).map((action) => asString(action.type).toLowerCase()), 16);
799
+ const currentTargetPaths = uniqueRuntimeStrings((workspace?.targets || []).map((target) => target.resolved_path || target.path), 16);
800
+ const currentArtifactKinds = uniqueRuntimeStrings((options.artifacts || []).map((artifact) => asString(artifact.kind).toLowerCase()), 16);
801
+ const recentJobIds = uniqueRuntimeStrings([options.jobId, ...(priorMemory?.recent_job_ids || [])], 12);
802
+ const recentActionTypes = uniqueRuntimeStrings([...currentActionTypes, ...(priorMemory?.recent_action_types || [])], 16);
803
+ const recentTargetPaths = uniqueRuntimeStrings([...currentTargetPaths, ...(priorMemory?.recent_target_paths || [])], 16);
804
+ const recentArtifactKinds = uniqueRuntimeStrings([...currentArtifactKinds, ...(priorMemory?.recent_artifact_kinds || [])], 16);
805
+ const jobCount = Math.max(recentJobIds.length, (priorMemory?.job_count || 0) + (options.jobId && options.jobId !== priorMemory?.latest_job_id ? 1 : 0));
806
+ const latestJobStatus = asString(options.jobStatus) || priorMemory?.latest_job_status || undefined;
807
+ const scopedRootPath = workspace?.repoManifest?.scoped_root_path
808
+ || priorMemory?.scoped_root_path
809
+ || workspace?.roots[0]?.resolved_path
810
+ || undefined;
811
+ const repoRoot = workspace?.repoManifest?.detected_repo_root
812
+ || workspace?.repoRoot
813
+ || priorMemory?.repo_root
814
+ || undefined;
815
+ const instructionDigest = workspace?.instructionBundle?.digest || priorMemory?.instruction_digest || undefined;
816
+ const updatedAt = asString(options.updatedAt) || new Date().toISOString();
817
+ return {
818
+ memory_id: `workspace_memory.${workspaceId}`,
819
+ workspace_id: workspaceId,
820
+ source: workspace ? "bridge_workspace_runtime" : (priorMemory?.source || "device_job_history"),
821
+ job_count: Math.max(0, jobCount),
822
+ recent_job_ids: recentJobIds,
823
+ recent_action_types: recentActionTypes,
824
+ recent_target_paths: recentTargetPaths,
825
+ recent_artifact_kinds: recentArtifactKinds,
826
+ latest_job_id: asString(options.jobId) || priorMemory?.latest_job_id || undefined,
827
+ latest_job_status: latestJobStatus,
828
+ latest_job_at: updatedAt,
829
+ instruction_digest: instructionDigest,
830
+ repo_root: repoRoot,
831
+ scoped_root_path: scopedRootPath,
832
+ summary: `Memoria curta do workspace ${workspaceId} com ${Math.max(0, jobCount)} job${jobCount === 1 ? "" : "s"} recente${jobCount === 1 ? "" : "s"}; ultima execucao ${latestJobStatus || "desconhecida"}.`,
833
+ };
834
+ }
835
+ async function buildInstructionBundle(options) {
836
+ const descriptor = options.descriptor;
837
+ const entrypoints = descriptor?.entrypoints?.length ? descriptor.entrypoints : ["AGENTS.md"];
838
+ const precedence = descriptor?.precedence?.length
839
+ ? descriptor.precedence
840
+ : ["nearest_target_agents_md", "workspace_repo_agents_md", "monorepo_root_agents_md"];
841
+ const resolver = asString(descriptor?.resolver) || "bridge_local_agents_md";
842
+ const discovered = [];
843
+ const seen = new Set();
844
+ const repoRoot = options.repoRoot;
845
+ for (const target of options.targets) {
846
+ let current = target.directory_path;
847
+ const upperBound = repoRoot || findBestMatchingRoot(current, options.roots)?.resolved_path || current;
848
+ while (true) {
849
+ for (const entrypoint of entrypoints) {
850
+ const candidate = path.join(current, entrypoint);
851
+ if (seen.has(candidate)) {
852
+ continue;
853
+ }
854
+ try {
855
+ const fileStat = await stat(candidate);
856
+ if (!fileStat.isFile()) {
857
+ continue;
858
+ }
859
+ const rawContent = await readFile(candidate, "utf8");
860
+ seen.add(candidate);
861
+ const scope = current === target.directory_path
862
+ ? "nearest_target"
863
+ : repoRoot && current === repoRoot
864
+ ? "repo_root"
865
+ : options.roots.some((root) => root.resolved_path === current)
866
+ ? "workspace_root"
867
+ : "ancestor";
868
+ discovered.push({
869
+ path: candidate,
870
+ scope,
871
+ content: clipInstructionText(rawContent),
872
+ content_char_count: rawContent.length,
873
+ });
874
+ }
875
+ catch {
876
+ // Ignore missing entrypoints.
877
+ }
878
+ }
879
+ if (current === upperBound) {
880
+ break;
881
+ }
882
+ const parent = path.dirname(current);
883
+ if (parent === current) {
884
+ break;
885
+ }
886
+ current = parent;
887
+ }
888
+ }
889
+ if (discovered.length === 0) {
890
+ return undefined;
891
+ }
892
+ const digest = createHash("sha1")
893
+ .update(discovered.map((item) => `${item.path}:${item.content_char_count}:${item.content}`).join("\n"))
894
+ .digest("hex");
895
+ return {
896
+ resolver,
897
+ entrypoints,
898
+ precedence,
899
+ digest,
900
+ source_count: discovered.length,
901
+ sources: discovered,
902
+ summary: `Carreguei ${discovered.length} arquivo${discovered.length === 1 ? "" : "s"} ${entrypoints.join(", ")} para o workspace atual.`,
903
+ };
904
+ }
905
+ export async function resolveWorkspaceContext(options) {
906
+ const baseCwd = options.baseCwd || process.cwd();
907
+ const declared = options.workspaceContext || inferRuntimeWorkspaceContextFromActions(options.actions || []);
908
+ if (!declared || !Array.isArray(declared.roots) || declared.roots.length === 0) {
909
+ return null;
910
+ }
911
+ const resolvedRoots = declared.roots
912
+ .map((root, index) => {
913
+ const resolvedPath = expandUserPathLike(root.path, baseCwd);
914
+ return {
915
+ ...root,
916
+ root_id: asString(root.root_id) || `workspace_root_${String(index + 1).padStart(2, "0")}`,
917
+ resolved_path: resolvedPath,
918
+ action_types: root.action_types || [],
919
+ };
920
+ })
921
+ .filter((root) => Boolean(root.resolved_path));
922
+ if (resolvedRoots.length === 0) {
923
+ return null;
924
+ }
925
+ const resolvedTargets = [];
926
+ for (const [index, target] of (declared.targets || []).entries()) {
927
+ const expandedBasePath = expandUserPathLike(target.path, baseCwd);
928
+ const resolvedPath = target.filename
929
+ ? path.join(expandedBasePath, target.filename)
930
+ : expandedBasePath;
931
+ const directoryPath = dirnameForTarget(target, resolvedPath);
932
+ const matchedRoot = findBestMatchingRoot(resolvedPath, resolvedRoots)
933
+ || findBestMatchingRoot(directoryPath, resolvedRoots);
934
+ if (!matchedRoot) {
935
+ throw new Error(`O caminho ${target.path} fica fora do workspace permitido.`);
936
+ }
937
+ resolvedTargets.push({
938
+ ...target,
939
+ target_id: asString(target.target_id) || `workspace_target_${String(index + 1).padStart(2, "0")}`,
940
+ resolved_path: resolvedPath,
941
+ directory_path: directoryPath,
942
+ root_id: matchedRoot.root_id,
943
+ });
944
+ }
945
+ const repoRoot = await findRepoRoot(resolvedTargets[0]?.directory_path || resolvedRoots[0].resolved_path);
946
+ const workspaceId = asString(declared.workspace_id) || "workspace_local_runtime";
947
+ const runtimeActionTypes = uniqueStrings((options.actions || []).map((action) => asString(action.type).toLowerCase()));
948
+ const targetActionTypes = uniqueStrings((declared.targets || []).map((target) => asString(target.action_type).toLowerCase()));
949
+ const destructiveActionTypes = new Set();
950
+ for (const target of declared.targets || []) {
951
+ if (asString(target.access_mode).toLowerCase() === "delete" && asString(target.action_type)) {
952
+ destructiveActionTypes.add(asString(target.action_type).toLowerCase());
953
+ }
954
+ }
955
+ for (const action of options.actions || []) {
956
+ const actionType = asString(action.type).toLowerCase();
957
+ if (!actionType) {
958
+ continue;
959
+ }
960
+ if (actionType === "trash_path" || actionType === "delete_file") {
961
+ destructiveActionTypes.add(actionType);
962
+ continue;
963
+ }
964
+ if (actionType === "apply_patch") {
965
+ const patchTargets = collectStructuredPatchTargets(asString(action.patch) || "");
966
+ if (patchTargets.some((item) => item.accessMode === "delete")) {
967
+ destructiveActionTypes.add(actionType);
968
+ }
969
+ }
970
+ }
971
+ const workspaceMemory = normalizeWorkspaceMemory(declared.workspace_memory, workspaceId);
972
+ const workspacePolicy = normalizeWorkspacePolicy(declared.workspace_policy, workspaceId, [...targetActionTypes, ...runtimeActionTypes], destructiveActionTypes);
973
+ const instructionBundle = await buildInstructionBundle({
974
+ descriptor: declared.instruction_bundle,
975
+ roots: resolvedRoots,
976
+ targets: resolvedTargets,
977
+ repoRoot,
978
+ });
979
+ const workspaceIndex = await buildWorkspaceIndex({
980
+ workspaceId,
981
+ roots: resolvedRoots,
982
+ repoRoot,
983
+ descriptor: declared.workspace_index,
984
+ });
985
+ const repoManifest = await buildRepoManifest({
986
+ workspaceId,
987
+ roots: resolvedRoots,
988
+ targets: resolvedTargets,
989
+ repoRoot,
990
+ instructionBundle,
991
+ descriptor: declared.repo_manifest,
992
+ });
993
+ const defaultCwd = resolvedTargets.find((target) => target.kind === "shell_cwd")?.resolved_path
994
+ || resolvedRoots[0]?.resolved_path
995
+ || baseCwd;
996
+ return {
997
+ workspaceId,
998
+ roots: resolvedRoots,
999
+ targets: resolvedTargets,
1000
+ repoRoot,
1001
+ defaultCwd,
1002
+ instructionBundle,
1003
+ repoManifest,
1004
+ workspaceIndex,
1005
+ workspaceMemory,
1006
+ workspacePolicy,
1007
+ summary: `Workspace ${workspaceId} com ${resolvedRoots.length} root${resolvedRoots.length === 1 ? "" : "s"} e ${resolvedTargets.length} target${resolvedTargets.length === 1 ? "" : "s"}.`,
1008
+ };
1009
+ }
1010
+ export function assertPathInsideWorkspace(workspace, targetPath, options) {
1011
+ const resolvedPath = options?.filename
1012
+ ? path.join(expandUserPathLike(targetPath, options?.baseCwd || workspace.defaultCwd), options.filename)
1013
+ : expandUserPathLike(targetPath, options?.baseCwd || workspace.defaultCwd);
1014
+ const matchedRoot = findBestMatchingRoot(resolvedPath, workspace.roots)
1015
+ || findBestMatchingRoot(path.dirname(resolvedPath), workspace.roots);
1016
+ if (!matchedRoot) {
1017
+ throw new Error(`O caminho ${targetPath} fica fora do workspace permitido (${workspace.workspaceId}).`);
1018
+ }
1019
+ return resolvedPath;
1020
+ }
1021
+ export function assertCwdInsideWorkspace(workspace, cwd) {
1022
+ const resolvedCwd = expandUserPathLike(cwd || workspace.defaultCwd, workspace.defaultCwd);
1023
+ const matchedRoot = findBestMatchingRoot(resolvedCwd, workspace.roots);
1024
+ if (!matchedRoot) {
1025
+ throw new Error(`O diretorio ${cwd || workspace.defaultCwd} fica fora do workspace permitido (${workspace.workspaceId}).`);
1026
+ }
1027
+ return resolvedCwd;
1028
+ }
1029
+ export function assertActionAllowedByWorkspacePolicy(workspace, actionType) {
1030
+ const normalizedActionType = asString(actionType).toLowerCase();
1031
+ if (!normalizedActionType || !WORKSPACE_POLICY_ALL_ACTIONS.has(normalizedActionType) && normalizedActionType !== "apply_patch") {
1032
+ return;
1033
+ }
1034
+ const workspacePolicy = workspace.workspacePolicy;
1035
+ if (!workspacePolicy) {
1036
+ return;
1037
+ }
1038
+ if (workspacePolicy.blocked_action_types.includes(normalizedActionType)) {
1039
+ throw new Error(`A action ${normalizedActionType} foi bloqueada pela policy ${workspacePolicy.profile_id} do workspace ${workspace.workspaceId}.`);
1040
+ }
1041
+ if (!workspacePolicy.allowed_action_types.includes(normalizedActionType)) {
1042
+ throw new Error(`A action ${normalizedActionType} nao esta permitida pela policy ${workspacePolicy.profile_id} do workspace ${workspace.workspaceId}.`);
1043
+ }
1044
+ }