ue-mcp 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context-cli.js +16 -16
- package/dist/engine-observer.js +68 -68
- package/dist/flow/guard.d.ts +26 -37
- package/dist/flow/guard.js +40 -25
- package/dist/flow/guard.js.map +1 -1
- package/dist/flow/guarded-bridge.d.ts +0 -10
- package/dist/flow/guarded-bridge.js +18 -23
- package/dist/flow/guarded-bridge.js.map +1 -1
- package/dist/flow/schema.d.ts +276 -16
- package/dist/flow/task-guards.d.ts +1 -1
- package/dist/flow/task-guards.js +44 -68
- package/dist/flow/task-guards.js.map +1 -1
- package/dist/instructions.js +130 -130
- package/dist/plugin-cli.js +299 -299
- package/dist/tool-counts.json +2 -2
- package/package.json +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.cpp +76 -76
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.h +27 -27
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +128 -128
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +63 -63
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +230 -230
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +323 -323
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +20 -20
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ChooserHandlers_Nested.cpp +537 -537
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +77 -77
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +353 -353
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +18 -18
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_InputRuntime.cpp +246 -246
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +597 -597
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +51 -51
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +878 -878
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +449 -449
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Function.cpp +275 -275
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Graph.cpp +551 -551
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +531 -531
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +28 -28
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +677 -677
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +822 -822
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +22 -22
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +218 -218
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +17 -17
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.cpp +46 -46
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.h +23 -23
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/MCPExternalRegistry.cpp +83 -83
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -14
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/UE_MCP_BridgeStatusModule.cpp +37 -37
- package/dist/manifest-signature.d.ts +0 -1
- package/dist/manifest-signature.js +0 -53
- package/dist/manifest-signature.js.map +0 -1
- package/dist/pie/schema.d.ts +0 -791
- package/dist/pie/schema.js +0 -151
- package/dist/pie/schema.js.map +0 -1
- package/dist/watch-cli.js.map +0 -1
- /package/plugin/ue_mcp_bridge/{ue_mcp_bridge.uplugin → UE_MCP_Bridge.uplugin} +0 -0
package/dist/context-cli.js
CHANGED
|
@@ -21,20 +21,20 @@ const CYAN = "\x1b[36m";
|
|
|
21
21
|
const YELLOW = "\x1b[33m";
|
|
22
22
|
const ACTIONS = new Set(["full", "lean", "micro", "status"]);
|
|
23
23
|
function printHelp() {
|
|
24
|
-
console.log(`
|
|
25
|
-
${BOLD}${CYAN}ue-mcp context${RESET} - control the context seeding strategy
|
|
26
|
-
|
|
27
|
-
${BOLD}Usage:${RESET}
|
|
28
|
-
ue-mcp context show the current strategy
|
|
29
|
-
ue-mcp context status show the current strategy
|
|
30
|
-
ue-mcp context full every action inline (largest seed, default)
|
|
31
|
-
ue-mcp context lean action names visible, descriptions on demand
|
|
32
|
-
ue-mcp context micro one gateway tool fronts everything (smallest)
|
|
33
|
-
|
|
34
|
-
A project path may be passed as the last argument, or an editor named with
|
|
35
|
-
--editor <name-or-path>; otherwise the .uproject in the current directory is
|
|
36
|
-
used. Restart your MCP client (/mcp in Claude Code) after changing the
|
|
37
|
-
strategy.
|
|
24
|
+
console.log(`
|
|
25
|
+
${BOLD}${CYAN}ue-mcp context${RESET} - control the context seeding strategy
|
|
26
|
+
|
|
27
|
+
${BOLD}Usage:${RESET}
|
|
28
|
+
ue-mcp context show the current strategy
|
|
29
|
+
ue-mcp context status show the current strategy
|
|
30
|
+
ue-mcp context full every action inline (largest seed, default)
|
|
31
|
+
ue-mcp context lean action names visible, descriptions on demand
|
|
32
|
+
ue-mcp context micro one gateway tool fronts everything (smallest)
|
|
33
|
+
|
|
34
|
+
A project path may be passed as the last argument, or an editor named with
|
|
35
|
+
--editor <name-or-path>; otherwise the .uproject in the current directory is
|
|
36
|
+
used. Restart your MCP client (/mcp in Claude Code) after changing the
|
|
37
|
+
strategy.
|
|
38
38
|
`);
|
|
39
39
|
}
|
|
40
40
|
function parseArgs() {
|
|
@@ -50,8 +50,8 @@ function parseArgs() {
|
|
|
50
50
|
target = takeEditorTarget(argv);
|
|
51
51
|
}
|
|
52
52
|
catch (e) {
|
|
53
|
-
console.log(`
|
|
54
|
-
${RED}${e instanceof EditorFlagError ? e.message : String(e)}${RESET}
|
|
53
|
+
console.log(`
|
|
54
|
+
${RED}${e instanceof EditorFlagError ? e.message : String(e)}${RESET}
|
|
55
55
|
`);
|
|
56
56
|
process.exit(1);
|
|
57
57
|
}
|
package/dist/engine-observer.js
CHANGED
|
@@ -65,19 +65,19 @@ function classify(pid, commandLine, responding, windowTitle) {
|
|
|
65
65
|
// seconds (interpreter start ~1.5s, CIM ~4s, and `tasklist` is no cheaper), so
|
|
66
66
|
// the whole probe is a single script and callers reach it only when the fast
|
|
67
67
|
// signal - the bridge socket itself - has already failed.
|
|
68
|
-
const WINDOWS_PROCESS_SCRIPT = `
|
|
69
|
-
$ErrorActionPreference = 'SilentlyContinue'
|
|
70
|
-
$procs = Get-CimInstance Win32_Process -Filter "Name LIKE 'UnrealEditor%'"
|
|
71
|
-
$out = foreach ($p in $procs) {
|
|
72
|
-
$ps = Get-Process -Id $p.ProcessId -ErrorAction SilentlyContinue
|
|
73
|
-
[pscustomobject]@{
|
|
74
|
-
pid = $p.ProcessId
|
|
75
|
-
cmd = $p.CommandLine
|
|
76
|
-
responding = $(if ($ps) { [bool]$ps.Responding } else { $true })
|
|
77
|
-
title = $(if ($ps) { $ps.MainWindowTitle } else { '' })
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
@($out) | ConvertTo-Json -Compress -Depth 3
|
|
68
|
+
const WINDOWS_PROCESS_SCRIPT = `
|
|
69
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
70
|
+
$procs = Get-CimInstance Win32_Process -Filter "Name LIKE 'UnrealEditor%'"
|
|
71
|
+
$out = foreach ($p in $procs) {
|
|
72
|
+
$ps = Get-Process -Id $p.ProcessId -ErrorAction SilentlyContinue
|
|
73
|
+
[pscustomobject]@{
|
|
74
|
+
pid = $p.ProcessId
|
|
75
|
+
cmd = $p.CommandLine
|
|
76
|
+
responding = $(if ($ps) { [bool]$ps.Responding } else { $true })
|
|
77
|
+
title = $(if ($ps) { $ps.MainWindowTitle } else { '' })
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
@($out) | ConvertTo-Json -Compress -Depth 3
|
|
81
81
|
`;
|
|
82
82
|
// The process table is polled from wait loops and status calls; a short TTL
|
|
83
83
|
// keeps a burst of callers from paying for the same query repeatedly, while
|
|
@@ -297,61 +297,61 @@ export function readLogState(projectPath, tailLines = 25) {
|
|
|
297
297
|
* so it can be read from outside even when the process is not pumping. This is
|
|
298
298
|
* the only way to see a dialog that appears before the plugin loads.
|
|
299
299
|
*/
|
|
300
|
-
const WINDOWS_DIALOG_SCRIPT = `
|
|
301
|
-
$ErrorActionPreference = 'SilentlyContinue'
|
|
302
|
-
$sig = @'
|
|
303
|
-
using System;
|
|
304
|
-
using System.Text;
|
|
305
|
-
using System.Runtime.InteropServices;
|
|
306
|
-
public class UeMcpWin {
|
|
307
|
-
public delegate bool EnumProc(IntPtr hWnd, IntPtr lParam);
|
|
308
|
-
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc cb, IntPtr p);
|
|
309
|
-
[DllImport("user32.dll")] public static extern bool EnumChildWindows(IntPtr h, EnumProc cb, IntPtr p);
|
|
310
|
-
[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetWindowTextW(IntPtr h, StringBuilder s, int n);
|
|
311
|
-
[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetClassNameW(IntPtr h, StringBuilder s, int n);
|
|
312
|
-
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
|
|
313
|
-
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h);
|
|
314
|
-
[DllImport("user32.dll")] public static extern bool IsHungAppWindow(IntPtr h);
|
|
315
|
-
public static string Text(IntPtr h) {
|
|
316
|
-
StringBuilder sb = new StringBuilder(1024);
|
|
317
|
-
GetWindowTextW(h, sb, sb.Capacity);
|
|
318
|
-
return sb.ToString();
|
|
319
|
-
}
|
|
320
|
-
public static string Cls(IntPtr h) {
|
|
321
|
-
StringBuilder sb = new StringBuilder(256);
|
|
322
|
-
GetClassNameW(h, sb, sb.Capacity);
|
|
323
|
-
return sb.ToString();
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
'@
|
|
327
|
-
Add-Type -TypeDefinition $sig -Language CSharp | Out-Null
|
|
328
|
-
$targetPids = @(__PIDS__)
|
|
329
|
-
$results = New-Object System.Collections.ArrayList
|
|
330
|
-
$top = [UeMcpWin+EnumProc]{
|
|
331
|
-
param($h, $l)
|
|
332
|
-
$procId = 0
|
|
333
|
-
[void][UeMcpWin]::GetWindowThreadProcessId($h, [ref]$procId)
|
|
334
|
-
if ($targetPids -contains [int]$procId -and [UeMcpWin]::IsWindowVisible($h)) {
|
|
335
|
-
$children = New-Object System.Collections.ArrayList
|
|
336
|
-
$child = [UeMcpWin+EnumProc]{
|
|
337
|
-
param($c, $cl)
|
|
338
|
-
$t = [UeMcpWin]::Text($c)
|
|
339
|
-
if ($t -and $t.Trim().Length -gt 0) { [void]$children.Add($t) }
|
|
340
|
-
return $true
|
|
341
|
-
}
|
|
342
|
-
[void][UeMcpWin]::EnumChildWindows($h, $child, [IntPtr]::Zero)
|
|
343
|
-
[void]$results.Add([pscustomobject]@{
|
|
344
|
-
handle = $h.ToString()
|
|
345
|
-
className = [UeMcpWin]::Cls($h)
|
|
346
|
-
title = [UeMcpWin]::Text($h)
|
|
347
|
-
text = @($children)
|
|
348
|
-
hung = [bool][UeMcpWin]::IsHungAppWindow($h)
|
|
349
|
-
})
|
|
350
|
-
}
|
|
351
|
-
return $true
|
|
352
|
-
}
|
|
353
|
-
[void][UeMcpWin]::EnumWindows($top, [IntPtr]::Zero)
|
|
354
|
-
@($results) | ConvertTo-Json -Compress -Depth 4
|
|
300
|
+
const WINDOWS_DIALOG_SCRIPT = `
|
|
301
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
302
|
+
$sig = @'
|
|
303
|
+
using System;
|
|
304
|
+
using System.Text;
|
|
305
|
+
using System.Runtime.InteropServices;
|
|
306
|
+
public class UeMcpWin {
|
|
307
|
+
public delegate bool EnumProc(IntPtr hWnd, IntPtr lParam);
|
|
308
|
+
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc cb, IntPtr p);
|
|
309
|
+
[DllImport("user32.dll")] public static extern bool EnumChildWindows(IntPtr h, EnumProc cb, IntPtr p);
|
|
310
|
+
[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetWindowTextW(IntPtr h, StringBuilder s, int n);
|
|
311
|
+
[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetClassNameW(IntPtr h, StringBuilder s, int n);
|
|
312
|
+
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
|
|
313
|
+
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h);
|
|
314
|
+
[DllImport("user32.dll")] public static extern bool IsHungAppWindow(IntPtr h);
|
|
315
|
+
public static string Text(IntPtr h) {
|
|
316
|
+
StringBuilder sb = new StringBuilder(1024);
|
|
317
|
+
GetWindowTextW(h, sb, sb.Capacity);
|
|
318
|
+
return sb.ToString();
|
|
319
|
+
}
|
|
320
|
+
public static string Cls(IntPtr h) {
|
|
321
|
+
StringBuilder sb = new StringBuilder(256);
|
|
322
|
+
GetClassNameW(h, sb, sb.Capacity);
|
|
323
|
+
return sb.ToString();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
'@
|
|
327
|
+
Add-Type -TypeDefinition $sig -Language CSharp | Out-Null
|
|
328
|
+
$targetPids = @(__PIDS__)
|
|
329
|
+
$results = New-Object System.Collections.ArrayList
|
|
330
|
+
$top = [UeMcpWin+EnumProc]{
|
|
331
|
+
param($h, $l)
|
|
332
|
+
$procId = 0
|
|
333
|
+
[void][UeMcpWin]::GetWindowThreadProcessId($h, [ref]$procId)
|
|
334
|
+
if ($targetPids -contains [int]$procId -and [UeMcpWin]::IsWindowVisible($h)) {
|
|
335
|
+
$children = New-Object System.Collections.ArrayList
|
|
336
|
+
$child = [UeMcpWin+EnumProc]{
|
|
337
|
+
param($c, $cl)
|
|
338
|
+
$t = [UeMcpWin]::Text($c)
|
|
339
|
+
if ($t -and $t.Trim().Length -gt 0) { [void]$children.Add($t) }
|
|
340
|
+
return $true
|
|
341
|
+
}
|
|
342
|
+
[void][UeMcpWin]::EnumChildWindows($h, $child, [IntPtr]::Zero)
|
|
343
|
+
[void]$results.Add([pscustomobject]@{
|
|
344
|
+
handle = $h.ToString()
|
|
345
|
+
className = [UeMcpWin]::Cls($h)
|
|
346
|
+
title = [UeMcpWin]::Text($h)
|
|
347
|
+
text = @($children)
|
|
348
|
+
hung = [bool][UeMcpWin]::IsHungAppWindow($h)
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
return $true
|
|
352
|
+
}
|
|
353
|
+
[void][UeMcpWin]::EnumWindows($top, [IntPtr]::Zero)
|
|
354
|
+
@($results) | ConvertTo-Json -Compress -Depth 4
|
|
355
355
|
`;
|
|
356
356
|
/**
|
|
357
357
|
* Visible top-level windows belonging to the given PIDs, with their child text.
|
package/dist/flow/guard.d.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* guards/interceptors but agnostic to what any guard does.
|
|
2
|
+
* The bridge's binding of flowkit's guard pipeline.
|
|
4
3
|
*
|
|
5
|
-
* Every mutating and non-mutating action crosses `IBridge.call`.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* policy, rate limiting, or any concrete concern - those are guards.
|
|
4
|
+
* Every mutating and non-mutating action crosses `IBridge.call`. That seam is
|
|
5
|
+
* guarded: each guard may inspect the call, run a `before` hook that can veto
|
|
6
|
+
* it (throw) or act on it (e.g. check a file out), and an `after` hook that can
|
|
7
|
+
* observe or replace the result. The chain itself knows nothing about source
|
|
8
|
+
* control, policy, rate limiting, or any concrete concern - those are guards.
|
|
11
9
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* The pipeline, its ordering, and the `guard.<name>.<phase>` task convention
|
|
11
|
+
* live in `@db-lyon/flowkit/guard`, because none of that is about Unreal. What
|
|
12
|
+
* stays here is what is: the shape of a bridge call, and the write
|
|
13
|
+
* classification in `write-methods.ts` that decides which content paths a call
|
|
14
|
+
* is about to modify.
|
|
16
15
|
*/
|
|
16
|
+
import { GuardRegistry as FlowkitGuardRegistry, type Guard, type GuardContext } from "@db-lyon/flowkit/guard";
|
|
17
17
|
import type { IBridge } from "../bridge.js";
|
|
18
18
|
import type { EditorSession } from "../session.js";
|
|
19
19
|
import { type WriteClassification } from "./write-methods.js";
|
|
20
20
|
/** Resolve a UE content path to an absolute on-disk file, or null if it does not exist. */
|
|
21
21
|
export type ResolveExistingFile = (contentPath: string) => string | null;
|
|
22
|
-
/** Per-call execution context passed to every guard.
|
|
23
|
-
export interface CallContext {
|
|
22
|
+
/** Per-call execution context passed to every guard. */
|
|
23
|
+
export interface CallContext extends GuardContext {
|
|
24
24
|
readonly method: string;
|
|
25
25
|
readonly params: Record<string, unknown>;
|
|
26
26
|
readonly timeoutMs?: number;
|
|
@@ -28,36 +28,25 @@ export interface CallContext {
|
|
|
28
28
|
readonly bridge: IBridge;
|
|
29
29
|
/** The editor this call is bound to. Absent only for a bridge built outside a session. */
|
|
30
30
|
readonly session?: EditorSession;
|
|
31
|
-
/** Scratch space shared across guards for the life of one call. */
|
|
32
|
-
readonly meta: Map<string, unknown>;
|
|
33
31
|
/** Lazy: how this call classifies as a write (which content paths it touches). Cached. */
|
|
34
32
|
write(): WriteClassification;
|
|
35
33
|
/** Lazy: absolute, existing on-disk files this call will modify (subset of write paths). Cached. */
|
|
36
34
|
writeFiles(): string[];
|
|
37
35
|
}
|
|
38
36
|
/**
|
|
39
|
-
* A guard on the bridge pipeline.
|
|
40
|
-
*
|
|
41
|
-
* gating are all just guards.
|
|
37
|
+
* A guard on the bridge pipeline. Guards are agnostic: source control, access
|
|
38
|
+
* policy, audit, rate limiting, and approval gating are all just guards.
|
|
42
39
|
*/
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
/** Lower runs first in `before` and last in `after`. Default 0. */
|
|
47
|
-
readonly order?: number;
|
|
48
|
-
/** Whether this guard participates for a given call. Default: always. */
|
|
49
|
-
appliesTo?(ctx: CallContext): boolean | Promise<boolean>;
|
|
50
|
-
/** Runs before the call. Throw to DENY the call; side effects are allowed. */
|
|
51
|
-
before?(ctx: CallContext): Promise<void>;
|
|
52
|
-
/** Runs after a successful call. Return a value to replace the result; return nothing to leave it. */
|
|
53
|
-
after?(ctx: CallContext, result: unknown): Promise<unknown | void>;
|
|
54
|
-
}
|
|
55
|
-
/** An ordered set of guards. Built-in guards register directly; plugin guards are discovered. */
|
|
56
|
-
export declare class GuardRegistry {
|
|
57
|
-
private guards;
|
|
58
|
-
register(guard: BridgeGuard): this;
|
|
59
|
-
list(): readonly BridgeGuard[];
|
|
60
|
-
get size(): number;
|
|
40
|
+
export type BridgeGuard = Guard<CallContext, unknown>;
|
|
41
|
+
/** The bridge's guard set. Built-in guards register directly; plugin guards are discovered. */
|
|
42
|
+
export declare class GuardRegistry extends FlowkitGuardRegistry<CallContext, unknown> {
|
|
61
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The scope a `guard.<name>.<phase>Write` task binds to: the call resolves to
|
|
46
|
+
* existing on-disk files it is about to modify. Declared here rather than in
|
|
47
|
+
* the task layer so the hand-written and task-backed guards agree on what
|
|
48
|
+
* "write" means.
|
|
49
|
+
*/
|
|
50
|
+
export declare function writeScope(ctx: CallContext): boolean;
|
|
62
51
|
/** Build the per-call context, wiring the lazy write-enrichment helpers. */
|
|
63
52
|
export declare function makeCallContext(method: string, params: Record<string, unknown>, timeoutMs: number | undefined, rawBridge: IBridge, resolveExistingFile: ResolveExistingFile, session?: EditorSession): CallContext;
|
package/dist/flow/guard.js
CHANGED
|
@@ -1,34 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bridge's binding of flowkit's guard pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Every mutating and non-mutating action crosses `IBridge.call`. That seam is
|
|
5
|
+
* guarded: each guard may inspect the call, run a `before` hook that can veto
|
|
6
|
+
* it (throw) or act on it (e.g. check a file out), and an `after` hook that can
|
|
7
|
+
* observe or replace the result. The chain itself knows nothing about source
|
|
8
|
+
* control, policy, rate limiting, or any concrete concern - those are guards.
|
|
9
|
+
*
|
|
10
|
+
* The pipeline, its ordering, and the `guard.<name>.<phase>` task convention
|
|
11
|
+
* live in `@db-lyon/flowkit/guard`, because none of that is about Unreal. What
|
|
12
|
+
* stays here is what is: the shape of a bridge call, and the write
|
|
13
|
+
* classification in `write-methods.ts` that decides which content paths a call
|
|
14
|
+
* is about to modify.
|
|
15
|
+
*/
|
|
16
|
+
import { GuardRegistry as FlowkitGuardRegistry, guardContextBase, lazy, } from "@db-lyon/flowkit/guard";
|
|
1
17
|
import { classifyWrite } from "./write-methods.js";
|
|
2
|
-
/**
|
|
3
|
-
export class GuardRegistry {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
get size() {
|
|
15
|
-
return this.guards.length;
|
|
16
|
-
}
|
|
18
|
+
/** The bridge's guard set. Built-in guards register directly; plugin guards are discovered. */
|
|
19
|
+
export class GuardRegistry extends FlowkitGuardRegistry {
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The scope a `guard.<name>.<phase>Write` task binds to: the call resolves to
|
|
23
|
+
* existing on-disk files it is about to modify. Declared here rather than in
|
|
24
|
+
* the task layer so the hand-written and task-backed guards agree on what
|
|
25
|
+
* "write" means.
|
|
26
|
+
*/
|
|
27
|
+
export function writeScope(ctx) {
|
|
28
|
+
return ctx.writeFiles().length > 0;
|
|
17
29
|
}
|
|
18
30
|
/** Build the per-call context, wiring the lazy write-enrichment helpers. */
|
|
19
31
|
export function makeCallContext(method, params, timeoutMs, rawBridge, resolveExistingFile, session) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
const ctx = {
|
|
33
|
+
...guardContextBase(),
|
|
34
|
+
method,
|
|
35
|
+
params,
|
|
36
|
+
timeoutMs,
|
|
37
|
+
bridge: rawBridge,
|
|
38
|
+
session,
|
|
39
|
+
};
|
|
40
|
+
const write = lazy(ctx, "write", () => classifyWrite(method, params));
|
|
41
|
+
const writeFiles = lazy(ctx, "writeFiles", () => {
|
|
26
42
|
const c = write();
|
|
27
|
-
|
|
43
|
+
return c.writes
|
|
28
44
|
? c.contentPaths.map(resolveExistingFile).filter((f) => f !== null)
|
|
29
45
|
: [];
|
|
30
|
-
|
|
31
|
-
};
|
|
32
|
-
return { method, params, timeoutMs, bridge: rawBridge, session, meta: new Map(), write, writeFiles };
|
|
46
|
+
});
|
|
47
|
+
return Object.assign(ctx, { write, writeFiles });
|
|
33
48
|
}
|
|
34
49
|
//# sourceMappingURL=guard.js.map
|
package/dist/flow/guard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/flow/guard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/flow/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EACL,aAAa,IAAI,oBAAoB,EACrC,gBAAgB,EAChB,IAAI,GAGL,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,aAAa,EAA4B,MAAM,oBAAoB,CAAC;AA0B7E,+FAA+F;AAC/F,MAAM,OAAO,aAAc,SAAQ,oBAA0C;CAAG;AAEhF;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,GAAgB;IACzC,OAAO,GAAG,CAAC,UAAU,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AACrC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAC7B,MAAc,EACd,MAA+B,EAC/B,SAA6B,EAC7B,SAAkB,EAClB,mBAAwC,EACxC,OAAuB;IAEvB,MAAM,GAAG,GAAG;QACV,GAAG,gBAAgB,EAAE;QACrB,MAAM;QACN,MAAM;QACN,SAAS;QACT,MAAM,EAAE,SAAS;QACjB,OAAO;KACO,CAAC;IAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;QAClB,OAAO,CAAC,CAAC,MAAM;YACb,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;YAChF,CAAC,CAAC,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;AACnD,CAAC"}
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GuardedBridge - runs a `GuardRegistry` pipeline around the editor bridge.
|
|
3
|
-
*
|
|
4
|
-
* Before a call reaches Unreal, each applicable guard's `before` hook runs in
|
|
5
|
-
* order (any may throw to deny). After a successful call, each guard's `after`
|
|
6
|
-
* hook runs in reverse order and may replace the result. With an empty registry
|
|
7
|
-
* this is a pure pass-through, so it is always safe to install.
|
|
8
|
-
*
|
|
9
|
-
* Only `call` is gated; connection lifecycle delegates straight through.
|
|
10
|
-
*/
|
|
11
1
|
import type { BridgeTarget, IBridge } from "../bridge.js";
|
|
12
2
|
import type { EditorSession } from "../session.js";
|
|
13
3
|
import { GuardRegistry, type ResolveExistingFile } from "./guard.js";
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* GuardedBridge - runs a `GuardRegistry` pipeline around the editor bridge.
|
|
3
|
+
*
|
|
4
|
+
* Before a call reaches Unreal, each applicable guard's `before` hook runs in
|
|
5
|
+
* order (any may throw to deny). After a successful call, each guard's `after`
|
|
6
|
+
* hook runs in reverse order and may replace the result. With an empty registry
|
|
7
|
+
* this is a pure pass-through, so it is always safe to install.
|
|
8
|
+
*
|
|
9
|
+
* The pipeline itself is `runGuarded` from flowkit; what this class adds is the
|
|
10
|
+
* `IBridge` shape. Only `call` is gated; connection lifecycle delegates
|
|
11
|
+
* straight through.
|
|
12
|
+
*/
|
|
13
|
+
import { runGuarded } from "@db-lyon/flowkit/guard";
|
|
14
|
+
import { makeCallContext } from "./guard.js";
|
|
2
15
|
export class GuardedBridge {
|
|
3
16
|
inner;
|
|
4
17
|
registry;
|
|
@@ -26,32 +39,14 @@ export class GuardedBridge {
|
|
|
26
39
|
return this.inner.getTarget();
|
|
27
40
|
}
|
|
28
41
|
async call(method, params, timeoutMs) {
|
|
42
|
+
// `runGuarded` is already a pass-through on an empty registry, but building
|
|
43
|
+
// the context is not free and every bridge call lands here. Most servers run
|
|
44
|
+
// with no guards at all, so skip the allocation outright.
|
|
29
45
|
if (this.registry.size === 0) {
|
|
30
46
|
return this.inner.call(method, params, timeoutMs);
|
|
31
47
|
}
|
|
32
48
|
const ctx = makeCallContext(method, params ?? {}, timeoutMs, this.inner, this.resolveExistingFile, this.session);
|
|
33
|
-
|
|
34
|
-
const applicable = [];
|
|
35
|
-
for (const g of this.registry.list()) {
|
|
36
|
-
if (!g.appliesTo || (await g.appliesTo(ctx)))
|
|
37
|
-
applicable.push(g);
|
|
38
|
-
}
|
|
39
|
-
// before: in order. A throw denies the call and propagates unchanged.
|
|
40
|
-
for (const g of applicable) {
|
|
41
|
-
if (g.before)
|
|
42
|
-
await g.before(ctx);
|
|
43
|
-
}
|
|
44
|
-
let result = await this.inner.call(method, params, timeoutMs);
|
|
45
|
-
// after: in reverse order. A returned value replaces the result.
|
|
46
|
-
for (let i = applicable.length - 1; i >= 0; i--) {
|
|
47
|
-
const g = applicable[i];
|
|
48
|
-
if (g.after) {
|
|
49
|
-
const replaced = await g.after(ctx, result);
|
|
50
|
-
if (replaced !== undefined)
|
|
51
|
-
result = replaced;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
49
|
+
return runGuarded(ctx, this.registry, () => this.inner.call(method, params, timeoutMs));
|
|
55
50
|
}
|
|
56
51
|
}
|
|
57
52
|
//# sourceMappingURL=guarded-bridge.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guarded-bridge.js","sourceRoot":"","sources":["../../src/flow/guarded-bridge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"guarded-bridge.js","sourceRoot":"","sources":["../../src/flow/guarded-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGpD,OAAO,EAAiB,eAAe,EAA4B,MAAM,YAAY,CAAC;AAItF,MAAM,OAAO,aAAa;IAEL;IACA;IACA;IAGA;IANnB,YACmB,KAAc,EACd,QAAuB,EACvB,mBAAwC;IACzD;2EACuE;IACtD,OAAuB;QALvB,UAAK,GAAL,KAAK,CAAS;QACd,aAAQ,GAAR,QAAQ,CAAe;QACvB,wBAAmB,GAAnB,mBAAmB,CAAqB;QAGxC,YAAO,GAAP,OAAO,CAAgB;IACvC,CAAC;IAEJ,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;IAChC,CAAC;IAED,OAAO,CAAC,SAAkB;QACxB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAED,eAAe,CAAC,YAAoB,EAAE,UAAmB;QACvD,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAC9D,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,IAAI,CACR,MAAc,EACd,MAAgC,EAChC,SAAkB;QAElB,4EAA4E;QAC5E,6EAA6E;QAC7E,0DAA0D;QAC1D,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,GAAG,GAAG,eAAe,CACzB,MAAM,EACN,MAAM,IAAI,EAAE,EACZ,SAAS,EACT,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,mBAAmB,EACxB,IAAI,CAAC,OAAO,CACb,CAAC;QACF,OAAO,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAC1F,CAAC;CACF"}
|