@relayflows/sdk 2.0.32 → 2.0.34
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/cloud-mirror-step.d.ts +12 -0
- package/dist/cloud-mirror-step.d.ts.map +1 -1
- package/dist/cloud-mirror-step.js +20 -1
- package/dist/cloud-mirror-step.js.map +1 -1
- package/dist/cloud-transcript-codex.d.ts.map +1 -1
- package/dist/cloud-transcript-codex.js +90 -4
- package/dist/cloud-transcript-codex.js.map +1 -1
- package/dist/cloud-transcript-types.d.ts +19 -1
- package/dist/cloud-transcript-types.d.ts.map +1 -1
- package/dist/cloud-transcript-types.js.map +1 -1
- package/dist/cloud-transcript.d.ts +1 -1
- package/dist/cloud-transcript.d.ts.map +1 -1
- package/dist/cloud-transcript.js +10 -0
- package/dist/cloud-transcript.js.map +1 -1
- package/dist/communication/environment.d.ts +2 -0
- package/dist/communication/environment.d.ts.map +1 -1
- package/dist/communication/environment.js +13 -7
- package/dist/communication/environment.js.map +1 -1
- package/dist/flow-extension-compat.d.ts.map +1 -1
- package/dist/flow-extension-compat.js +3 -1
- package/dist/flow-extension-compat.js.map +1 -1
- package/dist/helper-reference.d.ts.map +1 -1
- package/dist/helper-reference.js +134 -6
- package/dist/helper-reference.js.map +1 -1
- package/dist/hosted-extension-declarations.d.ts.map +1 -1
- package/dist/hosted-extension-declarations.js +4 -0
- package/dist/hosted-extension-declarations.js.map +1 -1
- package/dist/hosted-extension-isolation.d.ts +19 -2
- package/dist/hosted-extension-isolation.d.ts.map +1 -1
- package/dist/hosted-extension-isolation.js +29 -3
- package/dist/hosted-extension-isolation.js.map +1 -1
- package/dist/hosted-extension-runtime.js +2 -2
- package/dist/hosted-extension-sandbox.d.ts +2 -0
- package/dist/hosted-extension-sandbox.d.ts.map +1 -1
- package/dist/hosted-extension-sandbox.js +21 -7
- package/dist/hosted-extension-sandbox.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/cloud-mirror-step.ts +33 -1
- package/src/cloud-transcript-codex.ts +96 -4
- package/src/cloud-transcript-types.ts +18 -1
- package/src/cloud-transcript.ts +9 -1
- package/src/communication/environment.ts +15 -8
- package/src/flow-extension-compat.ts +3 -1
- package/src/helper-reference.ts +131 -6
- package/src/hosted-extension-declarations.ts +4 -0
- package/src/hosted-extension-isolation.ts +38 -2
- package/src/hosted-extension-runtime.ts +2 -2
- package/src/hosted-extension-sandbox.ts +19 -4
- package/src/index.ts +2 -0
package/src/helper-reference.ts
CHANGED
|
@@ -27,7 +27,8 @@ export function helperNamespacesUsed(body: string, root: string): ReadonlySet<st
|
|
|
27
27
|
const program = parseFlowBody(body);
|
|
28
28
|
if (program === null) return textFallback(body, root);
|
|
29
29
|
const used = new Set<string>();
|
|
30
|
-
|
|
30
|
+
const scope = { rootFunctionFound: false };
|
|
31
|
+
walkReferences(program, root, false, scope, (node) => {
|
|
31
32
|
if (node.type !== 'MemberExpression') return;
|
|
32
33
|
const object = node.object as AstNode | undefined;
|
|
33
34
|
if (object?.type !== 'Identifier' || object.name !== root) return;
|
|
@@ -111,20 +112,144 @@ type AstNode = {
|
|
|
111
112
|
[key: string]: unknown;
|
|
112
113
|
};
|
|
113
114
|
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Depth-first over references that still resolve to the outer flow context.
|
|
117
|
+
*
|
|
118
|
+
* A nested callback/method parameter or lexical destructuring can reuse the
|
|
119
|
+
* context's spelling while binding an unrelated value. Counting its members
|
|
120
|
+
* would refuse the flow for a mount it never touches. Acorn gives us binding
|
|
121
|
+
* shapes, so scopes are tracked as syntax instead of guessed from text.
|
|
122
|
+
*/
|
|
123
|
+
function walkReferences(
|
|
124
|
+
node: AstNode,
|
|
125
|
+
root: string,
|
|
126
|
+
shadowed: boolean,
|
|
127
|
+
state: { rootFunctionFound: boolean },
|
|
128
|
+
visit: (node: AstNode) => void,
|
|
129
|
+
): void {
|
|
130
|
+
if (isFunction(node)) {
|
|
131
|
+
const parameters = Array.isArray(node.params) ? node.params.filter(isNode) : [];
|
|
132
|
+
const bindsParameter = parameters.some(parameter => patternBinds(parameter, root));
|
|
133
|
+
let bodyHidden = shadowed;
|
|
134
|
+
const isRootFunction = bindsParameter && !state.rootFunctionFound;
|
|
135
|
+
if (isRootFunction) state.rootFunctionFound = true;
|
|
136
|
+
else if (bindsParameter || (isNode(node.id) && patternBinds(node.id, root)) || functionVarBinds(node, root)) {
|
|
137
|
+
bodyHidden = true;
|
|
138
|
+
}
|
|
139
|
+
// Parameter initializers run before the function body and are outside a
|
|
140
|
+
// body-level `var` scope. Keep them visible unless the parameter list
|
|
141
|
+
// itself binds the root name (in which case all parameter references are
|
|
142
|
+
// local to that parameter environment).
|
|
143
|
+
const parameterHidden = shadowed || (bindsParameter && !isRootFunction);
|
|
144
|
+
for (const parameter of parameters) walkReferences(parameter, root, parameterHidden, state, visit);
|
|
145
|
+
const body = isNode(node.body) ? node.body : undefined;
|
|
146
|
+
if (body !== undefined) walkReferences(body, root, bodyHidden, state, visit);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
let hidden = shadowed;
|
|
150
|
+
if (node.type === 'BlockStatement' && blockBinds(node, root)) {
|
|
151
|
+
hidden = true;
|
|
152
|
+
} else if (node.type === 'CatchClause' && isNode(node.param) && patternBinds(node.param, root)) {
|
|
153
|
+
hidden = true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!hidden) visit(node);
|
|
117
157
|
for (const key of Object.keys(node)) {
|
|
118
158
|
if (key === 'type' || key === 'start' || key === 'end' || key === 'loc') continue;
|
|
119
159
|
const child = node[key];
|
|
120
160
|
if (Array.isArray(child)) {
|
|
121
|
-
for (const entry of child) if (isNode(entry))
|
|
161
|
+
for (const entry of child) if (isNode(entry)) walkReferences(entry, root, hidden, state, visit);
|
|
122
162
|
} else if (isNode(child)) {
|
|
123
|
-
|
|
163
|
+
walkReferences(child, root, hidden, state, visit);
|
|
124
164
|
}
|
|
125
165
|
}
|
|
126
166
|
}
|
|
127
167
|
|
|
168
|
+
function isFunction(node: AstNode): boolean {
|
|
169
|
+
return node.type === 'ArrowFunctionExpression'
|
|
170
|
+
|| node.type === 'FunctionExpression'
|
|
171
|
+
|| node.type === 'FunctionDeclaration';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Lexical declarations bind across their complete block, including TDZ. */
|
|
175
|
+
function blockBinds(node: AstNode, root: string): boolean {
|
|
176
|
+
const statements = Array.isArray(node.body) ? node.body.filter(isNode) : [];
|
|
177
|
+
return statements.some((statement) => {
|
|
178
|
+
if (statement.type === 'VariableDeclaration' && statement.kind !== 'var') {
|
|
179
|
+
const declarations = Array.isArray(statement.declarations)
|
|
180
|
+
? statement.declarations.filter(isNode) : [];
|
|
181
|
+
return declarations.some(declaration => isNode(declaration.id) && patternBinds(declaration.id, root));
|
|
182
|
+
}
|
|
183
|
+
return (statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration')
|
|
184
|
+
&& isNode(statement.id) && patternBinds(statement.id, root);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Identifier occurrences that introduce bindings, including nested patterns. */
|
|
189
|
+
function patternBinds(pattern: AstNode, root: string): boolean {
|
|
190
|
+
switch (pattern.type) {
|
|
191
|
+
case 'Identifier':
|
|
192
|
+
return pattern.name === root;
|
|
193
|
+
case 'AssignmentPattern':
|
|
194
|
+
return isNode(pattern.left) && patternBinds(pattern.left, root);
|
|
195
|
+
case 'RestElement':
|
|
196
|
+
return isNode(pattern.argument) && patternBinds(pattern.argument, root);
|
|
197
|
+
case 'ArrayPattern': {
|
|
198
|
+
const elements = Array.isArray(pattern.elements) ? pattern.elements : [];
|
|
199
|
+
return elements.some(element => isNode(element) && patternBinds(element, root));
|
|
200
|
+
}
|
|
201
|
+
case 'ObjectPattern': {
|
|
202
|
+
const properties = Array.isArray(pattern.properties) ? pattern.properties : [];
|
|
203
|
+
return properties.some(property => {
|
|
204
|
+
if (!isNode(property)) return false;
|
|
205
|
+
if (property.type === 'RestElement') {
|
|
206
|
+
return isNode(property.argument) && patternBinds(property.argument, root);
|
|
207
|
+
}
|
|
208
|
+
// Object keys are labels, not bindings. Only the value side introduces
|
|
209
|
+
// the local (including a default-value AssignmentPattern's left side).
|
|
210
|
+
return property.type === 'Property'
|
|
211
|
+
&& isNode(property.value)
|
|
212
|
+
&& patternBinds(property.value, root);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
default:
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** `var` is function-scoped, so a nested function's declaration hides the
|
|
221
|
+
* outer flow context for its entire body, including code before the
|
|
222
|
+
* declaration. Nested functions have their own var scopes and are skipped. */
|
|
223
|
+
function functionVarBinds(node: AstNode, root: string): boolean {
|
|
224
|
+
const body = isNode(node.body) ? node.body : undefined;
|
|
225
|
+
if (body === undefined) return false;
|
|
226
|
+
let found = false;
|
|
227
|
+
const scan = (current: AstNode): void => {
|
|
228
|
+
if (found) return;
|
|
229
|
+
if (current !== body && isFunction(current)) return;
|
|
230
|
+
if (current.type === 'VariableDeclaration' && current.kind === 'var') {
|
|
231
|
+
const declarations = Array.isArray(current.declarations)
|
|
232
|
+
? current.declarations.filter(isNode) : [];
|
|
233
|
+
if (declarations.some(declaration => isNode(declaration.id) && patternBinds(declaration.id, root))) {
|
|
234
|
+
found = true;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const key of Object.keys(current)) {
|
|
239
|
+
if (key === 'type' || key === 'start' || key === 'end' || key === 'loc') continue;
|
|
240
|
+
const child = current[key];
|
|
241
|
+
if (Array.isArray(child)) {
|
|
242
|
+
for (const entry of child) if (isNode(entry)) scan(entry);
|
|
243
|
+
} else if (isNode(child)) {
|
|
244
|
+
scan(child);
|
|
245
|
+
}
|
|
246
|
+
if (found) return;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
scan(body);
|
|
250
|
+
return found;
|
|
251
|
+
}
|
|
252
|
+
|
|
128
253
|
function isNode(value: unknown): value is AstNode {
|
|
129
254
|
return typeof value === 'object' && value !== null && typeof (value as { type?: unknown }).type === 'string';
|
|
130
255
|
}
|
|
@@ -67,6 +67,9 @@ const STRING_STARTS_WITH = Function.prototype.call.bind(
|
|
|
67
67
|
) as (value: string, search: string) => boolean;
|
|
68
68
|
const LOCK_HEX64 = /^[0-9a-f]{64}$/;
|
|
69
69
|
const LOCK_SHA = /^[0-9a-f]{40}$/;
|
|
70
|
+
// `name` reaches pluginStoreDirectory as a path segment, so it stays one
|
|
71
|
+
// lowercase kebab-case component: no separators, no `.`/`..`, no traversal.
|
|
72
|
+
const LOCK_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
70
73
|
const LOCK_OWNER = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/;
|
|
71
74
|
const LOCK_REPO = /^[A-Za-z0-9_.-]{1,100}$/;
|
|
72
75
|
const HTTPS_GITHUB = /^https:\/\/github\.com\//;
|
|
@@ -240,6 +243,7 @@ function parseHostedPluginLock(input: unknown): {
|
|
|
240
243
|
]) ||
|
|
241
244
|
entry.kind !== "flow-extension" ||
|
|
242
245
|
typeof entry.name !== "string" ||
|
|
246
|
+
!REGEXP_TEST(LOCK_NAME, entry.name) ||
|
|
243
247
|
typeof entry.version !== "string" ||
|
|
244
248
|
typeof entry.digest !== "string" ||
|
|
245
249
|
!REGEXP_TEST(LOCK_HEX64, entry.digest) ||
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
assertHostedInstallationAuthority,
|
|
12
12
|
assertHostedRuntimeAuthority,
|
|
13
|
+
loadHostedExtensionRuntime,
|
|
13
14
|
type HostedExtensionArtifact,
|
|
14
15
|
type HostedExtensionBase,
|
|
15
16
|
type HostedExtensionInstallation,
|
|
@@ -104,8 +105,42 @@ export interface RunHostedExtensionOptions {
|
|
|
104
105
|
readonly prlimitPath?: string;
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
export interface RunHostedSoftwareGardenBabysitterOptions
|
|
109
|
+
extends Omit<RunHostedExtensionOptions, 'installation' | 'base'> {
|
|
110
|
+
/** Exact reviewed Software Garden source inside its lock-backed project. */
|
|
111
|
+
readonly flowPath: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
107
114
|
export type HostedExtensionResult = HostedExtensionProtocolResult;
|
|
108
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Compose the canonical Software Garden base with its installed native
|
|
118
|
+
* Babysitter and execute one host-verified delivery. Base source, extension
|
|
119
|
+
* declarations, lock entries and stored bytes are captured as one opaque
|
|
120
|
+
* generation; the capability runner rechecks that generation immediately
|
|
121
|
+
* before the isolated process starts.
|
|
122
|
+
*
|
|
123
|
+
* Hosted callers use this entrypoint instead of the ordinary authored loader,
|
|
124
|
+
* which imports extension JavaScript in the host and therefore keeps refusing
|
|
125
|
+
* matched hosted handlers.
|
|
126
|
+
*/
|
|
127
|
+
export async function runHostedSoftwareGardenBabysitter(
|
|
128
|
+
options: RunHostedSoftwareGardenBabysitterOptions,
|
|
129
|
+
): Promise<HostedExtensionResult> {
|
|
130
|
+
const runtime = await loadHostedExtensionRuntime(options.flowPath);
|
|
131
|
+
return await runHostedCapabilityExtension({
|
|
132
|
+
installation: runtime.installation,
|
|
133
|
+
base: runtime.base,
|
|
134
|
+
dispatch: options.dispatch,
|
|
135
|
+
input: options.input,
|
|
136
|
+
babysitterTurn: options.babysitterTurn,
|
|
137
|
+
...(OBJECT_HAS_OWN(options, 'timeoutMs') ? { timeoutMs: options.timeoutMs } : {}),
|
|
138
|
+
...(OBJECT_HAS_OWN(options, 'bubblewrapPath') ? { bubblewrapPath: options.bubblewrapPath } : {}),
|
|
139
|
+
...(OBJECT_HAS_OWN(options, 'nodePath') ? { nodePath: options.nodePath } : {}),
|
|
140
|
+
...(OBJECT_HAS_OWN(options, 'prlimitPath') ? { prlimitPath: options.prlimitPath } : {}),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
109
144
|
/**
|
|
110
145
|
* Execute a capability-only hosted extension in a Linux mount/PID/network/user
|
|
111
146
|
* namespace. The extension is first imported inside that namespace. It sees
|
|
@@ -114,8 +149,9 @@ export type HostedExtensionResult = HostedExtensionProtocolResult;
|
|
|
114
149
|
* authenticated capability adapter and passes the original branded dispatch
|
|
115
150
|
* authority to that adapter out of band.
|
|
116
151
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
152
|
+
* The ordinary authored executor deliberately stays refused: it imports
|
|
153
|
+
* extension JavaScript in the host. `runHostedSoftwareGardenBabysitter` is the
|
|
154
|
+
* canonical composition boundary for a hosted Software Garden delivery.
|
|
119
155
|
*/
|
|
120
156
|
export async function runHostedCapabilityExtension(
|
|
121
157
|
options: RunHostedExtensionOptions,
|
|
@@ -26,7 +26,7 @@ const REALPATH = realpath;
|
|
|
26
26
|
const PATH_DIRNAME = dirname;
|
|
27
27
|
const PATH_JOIN = join;
|
|
28
28
|
const PATH_RESOLVE = resolve;
|
|
29
|
-
const SOFTWARE_FACTORY_SHA256 = '
|
|
29
|
+
const SOFTWARE_FACTORY_SHA256 = '8bbcf0e42f47d6bc6491a129935f96b791be03c8a5ed5b73d651c4e77913e2d7';
|
|
30
30
|
const ARRAY_IS_ARRAY = Array.isArray;
|
|
31
31
|
const OBJECT_FREEZE = Object.freeze;
|
|
32
32
|
const WEAK_MAP_GET = Function.prototype.call.bind(WeakMap.prototype.get) as <K extends object, V>(
|
|
@@ -243,7 +243,7 @@ async function baseAt(origin: string, generation: RuntimeGeneration): Promise<Ho
|
|
|
243
243
|
}
|
|
244
244
|
const value = frozenHostedPromiseValue({
|
|
245
245
|
name: 'software-factory',
|
|
246
|
-
version: '2.0.
|
|
246
|
+
version: '2.0.23',
|
|
247
247
|
});
|
|
248
248
|
WEAK_SET_ADD(BASE_AUTHORITY, value);
|
|
249
249
|
WEAK_MAP_SET(BASE_GENERATION, value, generation);
|
|
@@ -53,6 +53,22 @@ const PATH_RESOLVE = resolve;
|
|
|
53
53
|
const SPAWN = spawn;
|
|
54
54
|
const PROCESS_EXEC_PATH = process.execPath;
|
|
55
55
|
const PROCESS_NODE_VERSION = process.versions.node;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The host's own `@relayflows/surface` entry, resolved once while this module
|
|
59
|
+
* initializes. `require.resolve` consults `Module._resolveFilename`, which is
|
|
60
|
+
* writable: resolving lazily would run after authored flow code has had a
|
|
61
|
+
* chance to replace it and could hand the sandbox a different package. The
|
|
62
|
+
* lookup therefore happens before any authored module can load, and a failure
|
|
63
|
+
* is reported when a caller actually needs the root.
|
|
64
|
+
*/
|
|
65
|
+
const SURFACE_ENTRY: string | undefined = (() => {
|
|
66
|
+
try { return REALPATH_SYNC(CREATE_REQUIRE(import.meta.url).resolve('@relayflows/surface')); }
|
|
67
|
+
catch { return undefined; }
|
|
68
|
+
})();
|
|
69
|
+
|
|
70
|
+
/** @internal Seam for the regression that pins initialization-time capture. */
|
|
71
|
+
export function capturedSurfaceEntry(): string | undefined { return SURFACE_ENTRY; }
|
|
56
72
|
const PROCESS_PLATFORM = process.platform;
|
|
57
73
|
const PROMISE = Promise;
|
|
58
74
|
const EVENT_ON = Function.prototype.call.bind(EventEmitter.prototype.on) as (
|
|
@@ -109,7 +125,7 @@ const ADDRESS_SPACE_BYTES = 16 * 1024 * 1024 * 1024;
|
|
|
109
125
|
const DATA_BYTES = 3 * 1024 * 1024 * 1024;
|
|
110
126
|
const SURFACE_RUNTIME_SHA256 = OBJECT_FREEZE({
|
|
111
127
|
'flow.js': '4aaeacc55de3074f4d121ce7253c3be50a93757e6540ba8159889a9450d1c05c',
|
|
112
|
-
'helpers/providers.js': '
|
|
128
|
+
'helpers/providers.js': '4eb06d0d85ca0a3434bb2dbba7407e3d95eef2dfbedaeb0e2de0c0c0ea812457',
|
|
113
129
|
'provider-trigger.js': 'e2664c65397f93fb486eb6f1e756c7cec3f88b3851d79c23567cad986f80f1ff',
|
|
114
130
|
'schedule.js': '8fe72f176a75ec0b5f26e12db7a597575c259a2e2cbb59690f9dc20a5e63940b',
|
|
115
131
|
'triggers.js': '4a3515b571a318f6c7a5661f9310bc9af43e3faf20ea39903a9b363c51258e4c',
|
|
@@ -250,9 +266,8 @@ export async function runHostedExtensionSandbox(
|
|
|
250
266
|
|
|
251
267
|
async function resolveSurfaceRoot(expectedVersion: string, override?: string): Promise<string> {
|
|
252
268
|
if (override !== undefined) return await checkedSurfaceRoot(override, expectedVersion);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
catch { return unsupported('hosted extension cannot resolve @relayflows/surface'); }
|
|
269
|
+
const resolved = SURFACE_ENTRY;
|
|
270
|
+
if (resolved === undefined) return unsupported('hosted extension cannot resolve @relayflows/surface');
|
|
256
271
|
let directory = PATH_DIRNAME(resolved);
|
|
257
272
|
const root = PATH_PARSE(directory).root;
|
|
258
273
|
while (directory !== root) {
|
package/src/index.ts
CHANGED
|
@@ -61,6 +61,7 @@ export type { StepSpend } from './protocol.js';
|
|
|
61
61
|
export { SPEC_SCHEMA_VERSION } from './spec.js';
|
|
62
62
|
|
|
63
63
|
export {
|
|
64
|
+
runHostedSoftwareGardenBabysitter,
|
|
64
65
|
runHostedCapabilityExtension,
|
|
65
66
|
loadHostedExtensionRuntime,
|
|
66
67
|
type HostedExtensionArtifact,
|
|
@@ -70,6 +71,7 @@ export {
|
|
|
70
71
|
type HostedCapabilityAuthority,
|
|
71
72
|
type HostedBabysitterCapability,
|
|
72
73
|
type RunHostedExtensionOptions,
|
|
74
|
+
type RunHostedSoftwareGardenBabysitterOptions,
|
|
73
75
|
type HostedExtensionResult,
|
|
74
76
|
} from './hosted-extension-isolation.js';
|
|
75
77
|
export {
|