@go-go-golems/os-scripting 0.1.0
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/README.md +147 -0
- package/app/createAppStore.d.ts +89 -0
- package/app/createAppStore.js +90 -0
- package/app/index.d.ts +2 -0
- package/app/index.js +2 -0
- package/app/runtimeSessionLifecycleMiddleware.d.ts +6 -0
- package/app/runtimeSessionLifecycleMiddleware.js +42 -0
- package/features/runtimeSessions/capabilityPolicy.d.ts +12 -0
- package/features/runtimeSessions/capabilityPolicy.js +36 -0
- package/features/runtimeSessions/index.d.ts +3 -0
- package/features/runtimeSessions/index.js +3 -0
- package/features/runtimeSessions/runtimeSessionsSlice.d.ts +90 -0
- package/features/runtimeSessions/runtimeSessionsSlice.js +210 -0
- package/features/runtimeSessions/selectors.d.ts +16 -0
- package/features/runtimeSessions/selectors.js +40 -0
- package/hypercard/artifacts/artifactProjectionMiddleware.d.ts +1 -0
- package/hypercard/artifacts/artifactProjectionMiddleware.js +66 -0
- package/hypercard/artifacts/artifactRuntime.d.ts +22 -0
- package/hypercard/artifacts/artifactRuntime.js +137 -0
- package/hypercard/artifacts/artifactsSelectors.d.ts +10 -0
- package/hypercard/artifacts/artifactsSelectors.js +2 -0
- package/hypercard/artifacts/artifactsSlice.d.ts +38 -0
- package/hypercard/artifacts/artifactsSlice.js +94 -0
- package/hypercard/debug/RuntimeSurfaceDebugWindow.d.ts +7 -0
- package/hypercard/debug/RuntimeSurfaceDebugWindow.js +181 -0
- package/hypercard/debug/jsSessionDebugRegistry.d.ts +11 -0
- package/hypercard/debug/jsSessionDebugRegistry.js +43 -0
- package/hypercard/debug/runtimeDebugApp.d.ts +19 -0
- package/hypercard/debug/runtimeDebugApp.js +25 -0
- package/hypercard/debug/runtimeDebugRegistry.d.ts +6 -0
- package/hypercard/debug/runtimeDebugRegistry.js +49 -0
- package/hypercard/editor/CodeEditorWindow.d.ts +7 -0
- package/hypercard/editor/CodeEditorWindow.js +128 -0
- package/hypercard/editor/editorLaunch.d.ts +16 -0
- package/hypercard/editor/editorLaunch.js +58 -0
- package/hypercard/editor/runtimeSurfaceRef.d.ts +8 -0
- package/hypercard/editor/runtimeSurfaceRef.js +60 -0
- package/hypercard/index.d.ts +12 -0
- package/hypercard/index.js +12 -0
- package/hypercard/task-manager/TaskManagerWindow.d.ts +1 -0
- package/hypercard/task-manager/TaskManagerWindow.js +102 -0
- package/hypercard/task-manager/index.d.ts +6 -0
- package/hypercard/task-manager/index.js +6 -0
- package/hypercard/task-manager/jsSessionSource.d.ts +10 -0
- package/hypercard/task-manager/jsSessionSource.js +49 -0
- package/hypercard/task-manager/runtimeSessionSource.d.ts +26 -0
- package/hypercard/task-manager/runtimeSessionSource.js +123 -0
- package/hypercard/task-manager/taskManagerApp.d.ts +16 -0
- package/hypercard/task-manager/taskManagerApp.js +25 -0
- package/hypercard/task-manager/taskManagerRegistry.d.ts +10 -0
- package/hypercard/task-manager/taskManagerRegistry.js +75 -0
- package/hypercard/task-manager/types.d.ts +25 -0
- package/hypercard/task-manager/types.js +1 -0
- package/hypercard/timeline/hypercardCard.d.ts +5 -0
- package/hypercard/timeline/hypercardCard.js +104 -0
- package/index.d.ts +18 -0
- package/index.js +18 -0
- package/package.json +72 -0
- package/plugin-runtime/contracts.d.ts +116 -0
- package/plugin-runtime/contracts.js +32 -0
- package/plugin-runtime/fixtures/column-stack.vm.js +19 -0
- package/plugin-runtime/fixtures/dynamic-card.vm.js +13 -0
- package/plugin-runtime/fixtures/inventory-stack.vm.js +29 -0
- package/plugin-runtime/fixtures/kanban-card.vm.js +55 -0
- package/plugin-runtime/fixtures/loop-stack.vm.js +16 -0
- package/plugin-runtime/fixtures/patched-low-stock-handler.vm.js +3 -0
- package/plugin-runtime/fixtures/patched-low-stock-render.vm.js +5 -0
- package/plugin-runtime/index.d.ts +6 -0
- package/plugin-runtime/index.js +6 -0
- package/plugin-runtime/intentSchema.d.ts +3 -0
- package/plugin-runtime/intentSchema.js +25 -0
- package/plugin-runtime/jsEvalSupport.d.ts +6 -0
- package/plugin-runtime/jsEvalSupport.js +93 -0
- package/plugin-runtime/jsSessionService.d.ts +55 -0
- package/plugin-runtime/jsSessionService.js +136 -0
- package/plugin-runtime/quickJsSessionCore.d.ts +24 -0
- package/plugin-runtime/quickJsSessionCore.js +92 -0
- package/plugin-runtime/runtimeService.d.ts +34 -0
- package/plugin-runtime/runtimeService.js +248 -0
- package/plugin-runtime/runtimeSurfaceRegistry.d.ts +45 -0
- package/plugin-runtime/runtimeSurfaceRegistry.js +73 -0
- package/plugin-runtime/stack-bootstrap.vm.js +236 -0
- package/repl/attachedJsSessionRegistry.d.ts +25 -0
- package/repl/attachedJsSessionRegistry.js +81 -0
- package/repl/attachedRuntimeSessionRegistry.d.ts +11 -0
- package/repl/attachedRuntimeSessionRegistry.js +107 -0
- package/repl/hypercardReplDriver.d.ts +54 -0
- package/repl/hypercardReplDriver.js +600 -0
- package/repl/jsReplDriver.d.ts +8 -0
- package/repl/jsReplDriver.js +348 -0
- package/repl/jsSessionBroker.d.ts +21 -0
- package/repl/jsSessionBroker.js +75 -0
- package/repl/runtimeBroker.d.ts +43 -0
- package/repl/runtimeBroker.js +117 -0
- package/runtime-host/RuntimeSurfaceSessionHost.d.ts +8 -0
- package/runtime-host/RuntimeSurfaceSessionHost.js +384 -0
- package/runtime-host/fixtures/CardSessionHost.chat.vm.js +61 -0
- package/runtime-host/fixtures/CardSessionHost.list.vm.js +29 -0
- package/runtime-host/fixtures/CardSessionHost.nav.vm.js +101 -0
- package/runtime-host/fixtures/CardSessionHost.report.vm.js +34 -0
- package/runtime-host/pluginIntentRouting.d.ts +13 -0
- package/runtime-host/pluginIntentRouting.js +83 -0
- package/runtime-packages/index.d.ts +1 -0
- package/runtime-packages/index.js +1 -0
- package/runtime-packages/runtimePackageRegistry.d.ts +14 -0
- package/runtime-packages/runtimePackageRegistry.js +42 -0
- package/runtime-packages/ui.package.vm.js +84 -0
- package/runtime-packs/index.d.ts +1 -0
- package/runtime-packs/index.js +1 -0
- package/runtime-packs/runtimeSurfaceTypeRegistry.d.ts +20 -0
- package/runtime-packs/runtimeSurfaceTypeRegistry.js +37 -0
- package/runtime-session-manager/index.d.ts +2 -0
- package/runtime-session-manager/index.js +2 -0
- package/runtime-session-manager/runtimeOwnership.d.ts +10 -0
- package/runtime-session-manager/runtimeOwnership.js +19 -0
- package/runtime-session-manager/runtimeSessionManager.d.ts +47 -0
- package/runtime-session-manager/runtimeSessionManager.js +214 -0
- package/testRuntimeUi.d.ts +4 -0
- package/testRuntimeUi.js +39 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { createQuickJsSessionVm, disposeQuickJsSessionVm, evalQuickJsCodeOrThrow, evalQuickJsToNative, } from './quickJsSessionCore';
|
|
2
|
+
import { evaluateQuickJsSessionJs, getQuickJsSessionGlobalNames, installJsEvalBridge, } from './jsEvalSupport';
|
|
3
|
+
const DEFAULT_OPTIONS = {
|
|
4
|
+
memoryLimitBytes: 32 * 1024 * 1024,
|
|
5
|
+
stackLimitBytes: 1024 * 1024,
|
|
6
|
+
loadTimeoutMs: 1000,
|
|
7
|
+
evalTimeoutMs: 100,
|
|
8
|
+
inspectTimeoutMs: 50,
|
|
9
|
+
};
|
|
10
|
+
function normalizeOptions(options) {
|
|
11
|
+
return {
|
|
12
|
+
...DEFAULT_OPTIONS,
|
|
13
|
+
...options,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function toCoreOptions(options) {
|
|
17
|
+
return {
|
|
18
|
+
memoryLimitBytes: options.memoryLimitBytes,
|
|
19
|
+
stackLimitBytes: options.stackLimitBytes,
|
|
20
|
+
loadTimeoutMs: options.loadTimeoutMs,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export class JsSessionService {
|
|
24
|
+
options;
|
|
25
|
+
sessions = new Map();
|
|
26
|
+
constructor(options = {}) {
|
|
27
|
+
this.options = normalizeOptions(options);
|
|
28
|
+
}
|
|
29
|
+
getRecordOrThrow(sessionId) {
|
|
30
|
+
const record = this.sessions.get(sessionId);
|
|
31
|
+
if (!record) {
|
|
32
|
+
throw new Error(`JS session not found: ${sessionId}`);
|
|
33
|
+
}
|
|
34
|
+
return record;
|
|
35
|
+
}
|
|
36
|
+
async createRecord(request) {
|
|
37
|
+
const bootstrapSources = [
|
|
38
|
+
...(request.bootstrapSources ?? []),
|
|
39
|
+
];
|
|
40
|
+
if (request.preludeCode && request.preludeCode.trim().length > 0) {
|
|
41
|
+
bootstrapSources.push({
|
|
42
|
+
code: request.preludeCode,
|
|
43
|
+
filename: `${request.sessionId}.prelude.js`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const vm = await createQuickJsSessionVm(request.scopeId?.trim() || 'js-repl', request.sessionId, toCoreOptions(this.options), bootstrapSources);
|
|
47
|
+
installJsEvalBridge(vm, 'js-repl-bootstrap.js', this.options.loadTimeoutMs);
|
|
48
|
+
return {
|
|
49
|
+
vm,
|
|
50
|
+
title: request.title?.trim() || request.sessionId,
|
|
51
|
+
createdAt: new Date().toISOString(),
|
|
52
|
+
scopeId: request.scopeId?.trim() || 'js-repl',
|
|
53
|
+
preludeCode: request.preludeCode,
|
|
54
|
+
bootstrapSources: [...(request.bootstrapSources ?? [])],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async createSession(request) {
|
|
58
|
+
if (this.sessions.has(request.sessionId)) {
|
|
59
|
+
throw new Error(`JS session already exists: ${request.sessionId}`);
|
|
60
|
+
}
|
|
61
|
+
const record = await this.createRecord(request);
|
|
62
|
+
this.sessions.set(request.sessionId, record);
|
|
63
|
+
return this.getSummary(request.sessionId);
|
|
64
|
+
}
|
|
65
|
+
getSummary(sessionId) {
|
|
66
|
+
const record = this.getRecordOrThrow(sessionId);
|
|
67
|
+
return {
|
|
68
|
+
sessionId,
|
|
69
|
+
title: record.title,
|
|
70
|
+
createdAt: record.createdAt,
|
|
71
|
+
globalNames: this.getGlobalNames(sessionId),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
listSessions() {
|
|
75
|
+
return Array.from(this.sessions.keys())
|
|
76
|
+
.sort()
|
|
77
|
+
.map((sessionId) => this.getSummary(sessionId));
|
|
78
|
+
}
|
|
79
|
+
evaluate(sessionId, code) {
|
|
80
|
+
const record = this.getRecordOrThrow(sessionId);
|
|
81
|
+
return evaluateQuickJsSessionJs(record.vm, code, `${sessionId}.eval.js`, this.options.evalTimeoutMs, this.options.inspectTimeoutMs);
|
|
82
|
+
}
|
|
83
|
+
evaluateToNative(sessionId, code, filename, timeoutMs) {
|
|
84
|
+
const record = this.getRecordOrThrow(sessionId);
|
|
85
|
+
return evalQuickJsToNative(record.vm, code, filename, timeoutMs);
|
|
86
|
+
}
|
|
87
|
+
runCode(sessionId, code, filename, timeoutMs) {
|
|
88
|
+
const record = this.getRecordOrThrow(sessionId);
|
|
89
|
+
evalQuickJsCodeOrThrow(record.vm, code, filename, timeoutMs);
|
|
90
|
+
}
|
|
91
|
+
getGlobalNames(sessionId) {
|
|
92
|
+
const record = this.getRecordOrThrow(sessionId);
|
|
93
|
+
return getQuickJsSessionGlobalNames(record.vm, `${sessionId}.globals.js`, this.options.inspectTimeoutMs);
|
|
94
|
+
}
|
|
95
|
+
async resetSession(sessionId) {
|
|
96
|
+
const record = this.getRecordOrThrow(sessionId);
|
|
97
|
+
disposeQuickJsSessionVm(record.vm);
|
|
98
|
+
const nextRecord = await this.createRecord({
|
|
99
|
+
sessionId,
|
|
100
|
+
title: record.title,
|
|
101
|
+
scopeId: record.scopeId,
|
|
102
|
+
preludeCode: record.preludeCode,
|
|
103
|
+
bootstrapSources: record.bootstrapSources,
|
|
104
|
+
});
|
|
105
|
+
this.sessions.set(sessionId, {
|
|
106
|
+
...nextRecord,
|
|
107
|
+
createdAt: record.createdAt,
|
|
108
|
+
});
|
|
109
|
+
return this.getSummary(sessionId);
|
|
110
|
+
}
|
|
111
|
+
disposeSession(sessionId) {
|
|
112
|
+
const record = this.sessions.get(sessionId);
|
|
113
|
+
if (!record) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
this.sessions.delete(sessionId);
|
|
117
|
+
disposeQuickJsSessionVm(record.vm);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
clear() {
|
|
121
|
+
for (const record of this.sessions.values()) {
|
|
122
|
+
disposeQuickJsSessionVm(record.vm);
|
|
123
|
+
}
|
|
124
|
+
this.sessions.clear();
|
|
125
|
+
}
|
|
126
|
+
health() {
|
|
127
|
+
return {
|
|
128
|
+
ready: true,
|
|
129
|
+
sessions: Array.from(this.sessions.keys()).sort(),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
installPrelude(sessionId, code) {
|
|
133
|
+
const record = this.getRecordOrThrow(sessionId);
|
|
134
|
+
evalQuickJsCodeOrThrow(record.vm, code, `${sessionId}.install-prelude.js`, this.options.loadTimeoutMs);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { QuickJSContext, QuickJSRuntime, QuickJSWASMModule } from 'quickjs-emscripten-core';
|
|
2
|
+
export interface QuickJsSessionCoreOptions {
|
|
3
|
+
memoryLimitBytes: number;
|
|
4
|
+
stackLimitBytes: number;
|
|
5
|
+
loadTimeoutMs: number;
|
|
6
|
+
}
|
|
7
|
+
export interface QuickJsSessionVm {
|
|
8
|
+
scopeId: string;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
runtime: QuickJSRuntime;
|
|
11
|
+
context: QuickJSContext;
|
|
12
|
+
deadlineMs: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function getSharedQuickJsModule(): Promise<QuickJSWASMModule>;
|
|
15
|
+
export declare function toJsLiteral(value: unknown): string;
|
|
16
|
+
export declare function formatQuickJSError(errorDump: unknown): string;
|
|
17
|
+
export declare function withDeadline<T>(vm: QuickJsSessionVm, timeoutMs: number, fn: () => T): T;
|
|
18
|
+
export declare function evalQuickJsToNative<T>(vm: QuickJsSessionVm, code: string, filename: string, timeoutMs: number): T;
|
|
19
|
+
export declare function evalQuickJsCodeOrThrow(vm: QuickJsSessionVm, code: string, filename: string, timeoutMs: number): void;
|
|
20
|
+
export declare function createQuickJsSessionVm(scopeId: string, sessionId: string, options: QuickJsSessionCoreOptions, bootstrapSources?: Array<{
|
|
21
|
+
code: string;
|
|
22
|
+
filename: string;
|
|
23
|
+
}>): Promise<QuickJsSessionVm>;
|
|
24
|
+
export declare function disposeQuickJsSessionVm(vm: QuickJsSessionVm): void;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import SINGLEFILE_RELEASE_SYNC from '@jitl/quickjs-singlefile-mjs-release-sync';
|
|
2
|
+
import { newQuickJSWASMModule } from 'quickjs-emscripten';
|
|
3
|
+
let quickJsModulePromise = null;
|
|
4
|
+
export function getSharedQuickJsModule() {
|
|
5
|
+
if (!quickJsModulePromise) {
|
|
6
|
+
quickJsModulePromise = newQuickJSWASMModule(SINGLEFILE_RELEASE_SYNC);
|
|
7
|
+
}
|
|
8
|
+
return quickJsModulePromise;
|
|
9
|
+
}
|
|
10
|
+
export function toJsLiteral(value) {
|
|
11
|
+
const encoded = JSON.stringify(value);
|
|
12
|
+
return encoded === undefined ? 'undefined' : encoded;
|
|
13
|
+
}
|
|
14
|
+
export function formatQuickJSError(errorDump) {
|
|
15
|
+
if (typeof errorDump === 'string') {
|
|
16
|
+
return errorDump;
|
|
17
|
+
}
|
|
18
|
+
if (errorDump && typeof errorDump === 'object') {
|
|
19
|
+
const details = errorDump;
|
|
20
|
+
if (details.name && details.message) {
|
|
21
|
+
return `${details.name}: ${details.message}`;
|
|
22
|
+
}
|
|
23
|
+
if (details.message) {
|
|
24
|
+
return details.message;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return 'Unknown QuickJS runtime error';
|
|
28
|
+
}
|
|
29
|
+
export function withDeadline(vm, timeoutMs, fn) {
|
|
30
|
+
vm.deadlineMs = Date.now() + timeoutMs;
|
|
31
|
+
try {
|
|
32
|
+
return fn();
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
vm.deadlineMs = Number.POSITIVE_INFINITY;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function evalQuickJsToNative(vm, code, filename, timeoutMs) {
|
|
39
|
+
const context = vm.context;
|
|
40
|
+
const result = withDeadline(vm, timeoutMs, () => context.evalCode(code, filename));
|
|
41
|
+
if (result.error) {
|
|
42
|
+
const dumped = context.dump(result.error);
|
|
43
|
+
result.error.dispose();
|
|
44
|
+
throw new Error(formatQuickJSError(dumped));
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
return context.dump(result.value);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
result.value.dispose();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function evalQuickJsCodeOrThrow(vm, code, filename, timeoutMs) {
|
|
54
|
+
const context = vm.context;
|
|
55
|
+
const result = withDeadline(vm, timeoutMs, () => context.evalCode(code, filename));
|
|
56
|
+
if (result.error) {
|
|
57
|
+
const dumped = context.dump(result.error);
|
|
58
|
+
result.error.dispose();
|
|
59
|
+
throw new Error(formatQuickJSError(dumped));
|
|
60
|
+
}
|
|
61
|
+
result.value.dispose();
|
|
62
|
+
}
|
|
63
|
+
export async function createQuickJsSessionVm(scopeId, sessionId, options, bootstrapSources = []) {
|
|
64
|
+
const QuickJS = await getSharedQuickJsModule();
|
|
65
|
+
const runtime = QuickJS.newRuntime();
|
|
66
|
+
const context = runtime.newContext();
|
|
67
|
+
const vm = {
|
|
68
|
+
scopeId,
|
|
69
|
+
sessionId,
|
|
70
|
+
runtime,
|
|
71
|
+
context,
|
|
72
|
+
deadlineMs: Number.POSITIVE_INFINITY,
|
|
73
|
+
};
|
|
74
|
+
runtime.setMemoryLimit(options.memoryLimitBytes);
|
|
75
|
+
runtime.setMaxStackSize(options.stackLimitBytes);
|
|
76
|
+
runtime.setInterruptHandler(() => Date.now() > vm.deadlineMs);
|
|
77
|
+
try {
|
|
78
|
+
for (const source of bootstrapSources) {
|
|
79
|
+
evalQuickJsCodeOrThrow(vm, source.code, source.filename, options.loadTimeoutMs);
|
|
80
|
+
}
|
|
81
|
+
return vm;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
context.dispose();
|
|
85
|
+
runtime.dispose();
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function disposeQuickJsSessionVm(vm) {
|
|
90
|
+
vm.context.dispose();
|
|
91
|
+
vm.runtime.dispose();
|
|
92
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { RuntimeSurfaceId, RuntimeBundleMeta, RuntimeErrorPayload, RuntimeAction, SessionId, StackId } from './contracts';
|
|
2
|
+
import { type JsEvalResult } from './jsSessionService';
|
|
3
|
+
export interface QuickJSRuntimeServiceOptions {
|
|
4
|
+
memoryLimitBytes?: number;
|
|
5
|
+
stackLimitBytes?: number;
|
|
6
|
+
loadTimeoutMs?: number;
|
|
7
|
+
renderTimeoutMs?: number;
|
|
8
|
+
eventTimeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function toRuntimeError(error: unknown): RuntimeErrorPayload;
|
|
11
|
+
export declare class QuickJSRuntimeService {
|
|
12
|
+
private readonly options;
|
|
13
|
+
private readonly sessionService;
|
|
14
|
+
private readonly bundles;
|
|
15
|
+
private readonly instanceId;
|
|
16
|
+
constructor(options?: QuickJSRuntimeServiceOptions);
|
|
17
|
+
private getBundleOrThrow;
|
|
18
|
+
private readRuntimeBundleMeta;
|
|
19
|
+
private installRuntimePackages;
|
|
20
|
+
loadRuntimeBundle(stackId: StackId, sessionId: SessionId, packageIds: string[], code: string): Promise<RuntimeBundleMeta>;
|
|
21
|
+
defineRuntimeSurface(sessionId: SessionId, surfaceId: RuntimeSurfaceId, code: string, packId: string): RuntimeBundleMeta;
|
|
22
|
+
defineRuntimeSurfaceRender(sessionId: SessionId, surfaceId: RuntimeSurfaceId, code: string): RuntimeBundleMeta;
|
|
23
|
+
defineRuntimeSurfaceHandler(sessionId: SessionId, surfaceId: RuntimeSurfaceId, handler: string, code: string): RuntimeBundleMeta;
|
|
24
|
+
getRuntimeBundleMeta(sessionId: SessionId): RuntimeBundleMeta;
|
|
25
|
+
evaluateSessionJs(sessionId: SessionId, code: string): JsEvalResult;
|
|
26
|
+
getSessionGlobalNames(sessionId: SessionId): string[];
|
|
27
|
+
renderRuntimeSurface(sessionId: SessionId, surfaceId: RuntimeSurfaceId, state: unknown): unknown;
|
|
28
|
+
eventRuntimeSurface(sessionId: SessionId, surfaceId: RuntimeSurfaceId, handler: string, args: unknown, state: unknown): RuntimeAction[];
|
|
29
|
+
disposeSession(sessionId: SessionId): boolean;
|
|
30
|
+
health(): {
|
|
31
|
+
ready: true;
|
|
32
|
+
sessions: string[];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { validateRuntimeActions } from './intentSchema';
|
|
2
|
+
import { getRuntimePackageOrThrow, resolveRuntimePackageInstallOrder } from '../runtime-packages';
|
|
3
|
+
import { getRuntimeSurfaceTypeOrThrow } from '../runtime-packs';
|
|
4
|
+
import stackBootstrapSource from './stack-bootstrap.vm.js?raw';
|
|
5
|
+
import { toJsLiteral } from './quickJsSessionCore';
|
|
6
|
+
import { JsSessionService } from './jsSessionService';
|
|
7
|
+
const DEFAULT_OPTIONS = {
|
|
8
|
+
memoryLimitBytes: 32 * 1024 * 1024,
|
|
9
|
+
stackLimitBytes: 1024 * 1024,
|
|
10
|
+
loadTimeoutMs: 1000,
|
|
11
|
+
renderTimeoutMs: 100,
|
|
12
|
+
eventTimeoutMs: 100,
|
|
13
|
+
};
|
|
14
|
+
let runtimeServiceInstanceCounter = 1;
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
function validateRuntimeBundleMeta(stackId, sessionId, value) {
|
|
19
|
+
if (!isRecord(value)) {
|
|
20
|
+
throw new Error('Runtime bundle metadata must be an object');
|
|
21
|
+
}
|
|
22
|
+
const packageIds = value.packageIds;
|
|
23
|
+
if (!Array.isArray(packageIds) || packageIds.some((packageId) => typeof packageId !== 'string')) {
|
|
24
|
+
throw new Error('Runtime bundle metadata packageIds must be string[]');
|
|
25
|
+
}
|
|
26
|
+
const surfaces = value.surfaces;
|
|
27
|
+
if (!Array.isArray(surfaces) || surfaces.some((surfaceId) => typeof surfaceId !== 'string')) {
|
|
28
|
+
throw new Error('Runtime bundle metadata surfaces must be string[]');
|
|
29
|
+
}
|
|
30
|
+
const initialSurfaceState = value.initialSurfaceState;
|
|
31
|
+
if (initialSurfaceState !== undefined && !isRecord(initialSurfaceState)) {
|
|
32
|
+
throw new Error('Runtime bundle metadata initialSurfaceState must be an object when provided');
|
|
33
|
+
}
|
|
34
|
+
const surfaceTypes = value.surfaceTypes;
|
|
35
|
+
if (surfaceTypes !== undefined) {
|
|
36
|
+
if (!isRecord(surfaceTypes)) {
|
|
37
|
+
throw new Error('Runtime bundle metadata surfaceTypes must be an object when provided');
|
|
38
|
+
}
|
|
39
|
+
for (const [surfaceId, surfaceTypeId] of Object.entries(surfaceTypes)) {
|
|
40
|
+
if (typeof surfaceId !== 'string' || typeof surfaceTypeId !== 'string') {
|
|
41
|
+
throw new Error('Runtime bundle metadata surfaceTypes must be Record<string, string>');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
stackId,
|
|
47
|
+
sessionId,
|
|
48
|
+
declaredId: typeof value.declaredId === 'string' ? value.declaredId : undefined,
|
|
49
|
+
title: typeof value.title === 'string' ? value.title : 'Untitled Stack',
|
|
50
|
+
description: typeof value.description === 'string' ? value.description : undefined,
|
|
51
|
+
packageIds: packageIds.filter((packageId) => typeof packageId === 'string'),
|
|
52
|
+
initialSessionState: value.initialSessionState,
|
|
53
|
+
initialSurfaceState,
|
|
54
|
+
surfaces,
|
|
55
|
+
surfaceTypes: isRecord(surfaceTypes) ? Object.fromEntries(Object.entries(surfaceTypes).filter((entry) => typeof entry[1] === 'string')) : undefined,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function toRuntimeError(error) {
|
|
59
|
+
if (error instanceof Error) {
|
|
60
|
+
const interrupted = error.message.includes('interrupted');
|
|
61
|
+
return {
|
|
62
|
+
code: interrupted ? 'RUNTIME_TIMEOUT' : 'RUNTIME_ERROR',
|
|
63
|
+
message: error.message,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
code: 'UNKNOWN_ERROR',
|
|
68
|
+
message: String(error),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export class QuickJSRuntimeService {
|
|
72
|
+
options;
|
|
73
|
+
sessionService;
|
|
74
|
+
bundles = new Map();
|
|
75
|
+
instanceId;
|
|
76
|
+
constructor(options = {}) {
|
|
77
|
+
this.instanceId = `runtime-service-${runtimeServiceInstanceCounter++}`;
|
|
78
|
+
this.options = {
|
|
79
|
+
...DEFAULT_OPTIONS,
|
|
80
|
+
...options,
|
|
81
|
+
};
|
|
82
|
+
this.sessionService = new JsSessionService({
|
|
83
|
+
memoryLimitBytes: this.options.memoryLimitBytes,
|
|
84
|
+
stackLimitBytes: this.options.stackLimitBytes,
|
|
85
|
+
loadTimeoutMs: this.options.loadTimeoutMs,
|
|
86
|
+
evalTimeoutMs: this.options.eventTimeoutMs,
|
|
87
|
+
inspectTimeoutMs: this.options.loadTimeoutMs,
|
|
88
|
+
});
|
|
89
|
+
console.log('[QuickJSRuntimeService] Created service instance', {
|
|
90
|
+
instanceId: this.instanceId,
|
|
91
|
+
options: this.options,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
getBundleOrThrow(sessionId) {
|
|
95
|
+
const bundle = this.bundles.get(sessionId);
|
|
96
|
+
if (!bundle) {
|
|
97
|
+
console.error('[QuickJSRuntimeService] Runtime session not found', {
|
|
98
|
+
instanceId: this.instanceId,
|
|
99
|
+
sessionId,
|
|
100
|
+
availableSessions: Array.from(this.bundles.keys()),
|
|
101
|
+
});
|
|
102
|
+
throw new Error(`Runtime session not found: ${sessionId}`);
|
|
103
|
+
}
|
|
104
|
+
return bundle;
|
|
105
|
+
}
|
|
106
|
+
readRuntimeBundleMeta(stackId, sessionId) {
|
|
107
|
+
const meta = this.sessionService.evaluateToNative(sessionId, 'globalThis.__runtimeBundleHost.getMeta()', 'runtime-bundle-meta.js', this.options.loadTimeoutMs);
|
|
108
|
+
return validateRuntimeBundleMeta(stackId, sessionId, meta);
|
|
109
|
+
}
|
|
110
|
+
installRuntimePackages(sessionId, packageIds) {
|
|
111
|
+
const orderedPackageIds = resolveRuntimePackageInstallOrder(packageIds);
|
|
112
|
+
for (const packageId of orderedPackageIds) {
|
|
113
|
+
const runtimePackage = getRuntimePackageOrThrow(packageId);
|
|
114
|
+
this.sessionService.installPrelude(sessionId, runtimePackage.installPrelude);
|
|
115
|
+
}
|
|
116
|
+
return orderedPackageIds;
|
|
117
|
+
}
|
|
118
|
+
async loadRuntimeBundle(stackId, sessionId, packageIds, code) {
|
|
119
|
+
if (this.bundles.has(sessionId)) {
|
|
120
|
+
console.warn('[QuickJSRuntimeService] Refusing to load duplicate runtime session', {
|
|
121
|
+
instanceId: this.instanceId,
|
|
122
|
+
stackId,
|
|
123
|
+
sessionId,
|
|
124
|
+
availableSessions: Array.from(this.bundles.keys()),
|
|
125
|
+
});
|
|
126
|
+
throw new Error(`Runtime session already exists: ${sessionId}`);
|
|
127
|
+
}
|
|
128
|
+
console.log('[QuickJSRuntimeService] Loading runtime bundle', {
|
|
129
|
+
instanceId: this.instanceId,
|
|
130
|
+
stackId,
|
|
131
|
+
sessionId,
|
|
132
|
+
packageIds,
|
|
133
|
+
codeLength: code.length,
|
|
134
|
+
beforeSessions: Array.from(this.bundles.keys()),
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
await this.sessionService.createSession({
|
|
138
|
+
sessionId,
|
|
139
|
+
title: stackId,
|
|
140
|
+
scopeId: stackId,
|
|
141
|
+
bootstrapSources: [{ code: stackBootstrapSource, filename: 'stack-bootstrap.js' }],
|
|
142
|
+
});
|
|
143
|
+
const installedPackageIds = this.installRuntimePackages(sessionId, packageIds);
|
|
144
|
+
this.sessionService.runCode(sessionId, code, `${sessionId}.runtime-bundle.js`, this.options.loadTimeoutMs);
|
|
145
|
+
const bundle = this.readRuntimeBundleMeta(stackId, sessionId);
|
|
146
|
+
const declared = Array.from(new Set(bundle.packageIds)).sort();
|
|
147
|
+
const installed = Array.from(new Set(installedPackageIds)).sort();
|
|
148
|
+
if (declared.length !== installed.length || declared.some((packageId, index) => packageId !== installed[index])) {
|
|
149
|
+
throw new Error(`Runtime bundle packageIds mismatch. Declared: ${declared.join(', ') || '(none)'}; installed: ${installed.join(', ') || '(none)'}`);
|
|
150
|
+
}
|
|
151
|
+
this.bundles.set(sessionId, bundle);
|
|
152
|
+
console.log('[QuickJSRuntimeService] Loaded runtime bundle', {
|
|
153
|
+
instanceId: this.instanceId,
|
|
154
|
+
stackId,
|
|
155
|
+
sessionId,
|
|
156
|
+
declaredPackageIds: bundle.packageIds,
|
|
157
|
+
surfaces: bundle.surfaces,
|
|
158
|
+
afterSessions: Array.from(this.bundles.keys()),
|
|
159
|
+
});
|
|
160
|
+
return bundle;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.error('[QuickJSRuntimeService] Failed to load runtime bundle', {
|
|
164
|
+
instanceId: this.instanceId,
|
|
165
|
+
stackId,
|
|
166
|
+
sessionId,
|
|
167
|
+
packageIds,
|
|
168
|
+
message: error instanceof Error ? error.message : String(error),
|
|
169
|
+
});
|
|
170
|
+
this.sessionService.disposeSession(sessionId);
|
|
171
|
+
this.bundles.delete(sessionId);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
defineRuntimeSurface(sessionId, surfaceId, code, packId) {
|
|
176
|
+
const bundle = this.getBundleOrThrow(sessionId);
|
|
177
|
+
const normalizedPackId = typeof packId === 'string' ? packId.trim() : '';
|
|
178
|
+
if (!normalizedPackId) {
|
|
179
|
+
throw new Error(`Runtime surface packId is required for ${surfaceId}`);
|
|
180
|
+
}
|
|
181
|
+
getRuntimeSurfaceTypeOrThrow(normalizedPackId);
|
|
182
|
+
this.sessionService.runCode(sessionId, `globalThis.__runtimeBundleHost.defineRuntimeSurface(${toJsLiteral(surfaceId)}, (${code}), ${toJsLiteral(normalizedPackId)})`, `${sessionId}.define-runtime-surface.js`, this.options.loadTimeoutMs);
|
|
183
|
+
const next = this.readRuntimeBundleMeta(bundle.stackId, sessionId);
|
|
184
|
+
this.bundles.set(sessionId, next);
|
|
185
|
+
return next;
|
|
186
|
+
}
|
|
187
|
+
defineRuntimeSurfaceRender(sessionId, surfaceId, code) {
|
|
188
|
+
const bundle = this.getBundleOrThrow(sessionId);
|
|
189
|
+
this.sessionService.runCode(sessionId, `globalThis.__runtimeBundleHost.defineRuntimeSurfaceRender(${toJsLiteral(surfaceId)}, (${code}))`, `${sessionId}.define-runtime-surface-render.js`, this.options.loadTimeoutMs);
|
|
190
|
+
const next = this.readRuntimeBundleMeta(bundle.stackId, sessionId);
|
|
191
|
+
this.bundles.set(sessionId, next);
|
|
192
|
+
return next;
|
|
193
|
+
}
|
|
194
|
+
defineRuntimeSurfaceHandler(sessionId, surfaceId, handler, code) {
|
|
195
|
+
const bundle = this.getBundleOrThrow(sessionId);
|
|
196
|
+
this.sessionService.runCode(sessionId, `globalThis.__runtimeBundleHost.defineRuntimeSurfaceHandler(${toJsLiteral(surfaceId)}, ${toJsLiteral(handler)}, (${code}))`, `${sessionId}.define-runtime-surface-handler.js`, this.options.loadTimeoutMs);
|
|
197
|
+
const next = this.readRuntimeBundleMeta(bundle.stackId, sessionId);
|
|
198
|
+
this.bundles.set(sessionId, next);
|
|
199
|
+
return next;
|
|
200
|
+
}
|
|
201
|
+
getRuntimeBundleMeta(sessionId) {
|
|
202
|
+
const bundle = this.getBundleOrThrow(sessionId);
|
|
203
|
+
const next = this.readRuntimeBundleMeta(bundle.stackId, sessionId);
|
|
204
|
+
this.bundles.set(sessionId, next);
|
|
205
|
+
return next;
|
|
206
|
+
}
|
|
207
|
+
evaluateSessionJs(sessionId, code) {
|
|
208
|
+
this.getBundleOrThrow(sessionId);
|
|
209
|
+
return this.sessionService.evaluate(sessionId, code);
|
|
210
|
+
}
|
|
211
|
+
getSessionGlobalNames(sessionId) {
|
|
212
|
+
this.getBundleOrThrow(sessionId);
|
|
213
|
+
return this.sessionService.getGlobalNames(sessionId);
|
|
214
|
+
}
|
|
215
|
+
renderRuntimeSurface(sessionId, surfaceId, state) {
|
|
216
|
+
this.getBundleOrThrow(sessionId);
|
|
217
|
+
return this.sessionService.evaluateToNative(sessionId, `globalThis.__runtimeBundleHost.renderRuntimeSurface(${toJsLiteral(surfaceId)}, ${toJsLiteral(state)})`, `${sessionId}.render-runtime-surface.js`, this.options.renderTimeoutMs);
|
|
218
|
+
}
|
|
219
|
+
eventRuntimeSurface(sessionId, surfaceId, handler, args, state) {
|
|
220
|
+
this.getBundleOrThrow(sessionId);
|
|
221
|
+
const actions = this.sessionService.evaluateToNative(sessionId, `globalThis.__runtimeBundleHost.eventRuntimeSurface(${toJsLiteral(surfaceId)}, ${toJsLiteral(handler)}, ${toJsLiteral(args)}, ${toJsLiteral(state)})`, `${sessionId}.event-runtime-surface.js`, this.options.eventTimeoutMs);
|
|
222
|
+
return validateRuntimeActions(actions);
|
|
223
|
+
}
|
|
224
|
+
disposeSession(sessionId) {
|
|
225
|
+
if (!this.bundles.has(sessionId)) {
|
|
226
|
+
console.warn('[QuickJSRuntimeService] disposeSession missed', {
|
|
227
|
+
instanceId: this.instanceId,
|
|
228
|
+
sessionId,
|
|
229
|
+
availableSessions: Array.from(this.bundles.keys()),
|
|
230
|
+
});
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
this.bundles.delete(sessionId);
|
|
234
|
+
this.sessionService.disposeSession(sessionId);
|
|
235
|
+
console.log('[QuickJSRuntimeService] Disposed runtime session', {
|
|
236
|
+
instanceId: this.instanceId,
|
|
237
|
+
sessionId,
|
|
238
|
+
remainingSessions: Array.from(this.bundles.keys()),
|
|
239
|
+
});
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
health() {
|
|
243
|
+
return {
|
|
244
|
+
ready: true,
|
|
245
|
+
sessions: Array.from(this.bundles.keys()),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global registry of runtime surface definitions that need to be injected
|
|
3
|
+
* into runtime sessions after bundle loading.
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 1. Chat receives card.v2 event → calls registerRuntimeSurface(surfaceId, code, packId)
|
|
7
|
+
* 2. RuntimeSurfaceSessionHost finishes loadRuntimeBundle → calls injectPendingRuntimeSurfaces(service, sessionId)
|
|
8
|
+
* 3. injectPendingRuntimeSurfaces iterates the registry, calls service.defineRuntimeSurface() for each
|
|
9
|
+
*/
|
|
10
|
+
export interface RuntimeSurfaceDefinition {
|
|
11
|
+
surfaceId: string;
|
|
12
|
+
code: string;
|
|
13
|
+
packId: string;
|
|
14
|
+
registeredAt: number;
|
|
15
|
+
}
|
|
16
|
+
export interface RuntimeSurfaceInjectionFailure {
|
|
17
|
+
surfaceId: string;
|
|
18
|
+
error: string;
|
|
19
|
+
}
|
|
20
|
+
export interface RuntimeSurfaceInjectionResult {
|
|
21
|
+
injected: string[];
|
|
22
|
+
failed: RuntimeSurfaceInjectionFailure[];
|
|
23
|
+
}
|
|
24
|
+
/** Register a runtime surface definition for injection into future sessions. */
|
|
25
|
+
export declare function registerRuntimeSurface(surfaceId: string, code: string, packId: string): void;
|
|
26
|
+
/** Remove a runtime surface definition. */
|
|
27
|
+
export declare function unregisterRuntimeSurface(surfaceId: string): void;
|
|
28
|
+
/** Get all pending runtime surface definitions. */
|
|
29
|
+
export declare function getPendingRuntimeSurfaces(): RuntimeSurfaceDefinition[];
|
|
30
|
+
/** Check if a specific runtime surface is registered. */
|
|
31
|
+
export declare function hasRuntimeSurface(surfaceId: string): boolean;
|
|
32
|
+
/** Subscribe to registry changes. Returns unsubscribe function. */
|
|
33
|
+
export declare function onRegistryChange(fn: () => void): () => void;
|
|
34
|
+
/** Clear all registered runtime surfaces (for testing). */
|
|
35
|
+
export declare function clearRuntimeSurfaceRegistry(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Inject all pending runtime surfaces into a session.
|
|
38
|
+
* Returns the list of surface IDs that were successfully injected.
|
|
39
|
+
*/
|
|
40
|
+
export declare function injectPendingRuntimeSurfaces(service: {
|
|
41
|
+
defineRuntimeSurface(sessionId: string, surfaceId: string, code: string, packId: string): unknown;
|
|
42
|
+
}, sessionId: string): string[];
|
|
43
|
+
export declare function injectPendingRuntimeSurfacesWithReport(service: {
|
|
44
|
+
defineRuntimeSurface(sessionId: string, surfaceId: string, code: string, packId: string): unknown;
|
|
45
|
+
}, sessionId: string): RuntimeSurfaceInjectionResult;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global registry of runtime surface definitions that need to be injected
|
|
3
|
+
* into runtime sessions after bundle loading.
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 1. Chat receives card.v2 event → calls registerRuntimeSurface(surfaceId, code, packId)
|
|
7
|
+
* 2. RuntimeSurfaceSessionHost finishes loadRuntimeBundle → calls injectPendingRuntimeSurfaces(service, sessionId)
|
|
8
|
+
* 3. injectPendingRuntimeSurfaces iterates the registry, calls service.defineRuntimeSurface() for each
|
|
9
|
+
*/
|
|
10
|
+
const registry = new Map();
|
|
11
|
+
const listeners = new Set();
|
|
12
|
+
/** Register a runtime surface definition for injection into future sessions. */
|
|
13
|
+
export function registerRuntimeSurface(surfaceId, code, packId) {
|
|
14
|
+
const normalizedPackId = typeof packId === 'string' ? packId.trim() : '';
|
|
15
|
+
if (!normalizedPackId) {
|
|
16
|
+
throw new Error(`runtime surface packId is required for ${surfaceId}`);
|
|
17
|
+
}
|
|
18
|
+
registry.set(surfaceId, {
|
|
19
|
+
surfaceId,
|
|
20
|
+
code,
|
|
21
|
+
packId: normalizedPackId,
|
|
22
|
+
registeredAt: Date.now(),
|
|
23
|
+
});
|
|
24
|
+
listeners.forEach((fn) => fn());
|
|
25
|
+
}
|
|
26
|
+
/** Remove a runtime surface definition. */
|
|
27
|
+
export function unregisterRuntimeSurface(surfaceId) {
|
|
28
|
+
registry.delete(surfaceId);
|
|
29
|
+
listeners.forEach((fn) => fn());
|
|
30
|
+
}
|
|
31
|
+
/** Get all pending runtime surface definitions. */
|
|
32
|
+
export function getPendingRuntimeSurfaces() {
|
|
33
|
+
return Array.from(registry.values());
|
|
34
|
+
}
|
|
35
|
+
/** Check if a specific runtime surface is registered. */
|
|
36
|
+
export function hasRuntimeSurface(surfaceId) {
|
|
37
|
+
return registry.has(surfaceId);
|
|
38
|
+
}
|
|
39
|
+
/** Subscribe to registry changes. Returns unsubscribe function. */
|
|
40
|
+
export function onRegistryChange(fn) {
|
|
41
|
+
listeners.add(fn);
|
|
42
|
+
return () => listeners.delete(fn);
|
|
43
|
+
}
|
|
44
|
+
/** Clear all registered runtime surfaces (for testing). */
|
|
45
|
+
export function clearRuntimeSurfaceRegistry() {
|
|
46
|
+
registry.clear();
|
|
47
|
+
listeners.forEach((fn) => fn());
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Inject all pending runtime surfaces into a session.
|
|
51
|
+
* Returns the list of surface IDs that were successfully injected.
|
|
52
|
+
*/
|
|
53
|
+
export function injectPendingRuntimeSurfaces(service, sessionId) {
|
|
54
|
+
return injectPendingRuntimeSurfacesWithReport(service, sessionId).injected;
|
|
55
|
+
}
|
|
56
|
+
export function injectPendingRuntimeSurfacesWithReport(service, sessionId) {
|
|
57
|
+
const injected = [];
|
|
58
|
+
const failed = [];
|
|
59
|
+
for (const def of registry.values()) {
|
|
60
|
+
try {
|
|
61
|
+
service.defineRuntimeSurface(sessionId, def.surfaceId, def.code, def.packId);
|
|
62
|
+
injected.push(def.surfaceId);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
console.error(`[runtimeSurfaceRegistry] Failed to inject runtime surface ${def.surfaceId} into ${sessionId}:`, err);
|
|
66
|
+
failed.push({
|
|
67
|
+
surfaceId: def.surfaceId,
|
|
68
|
+
error: err instanceof Error ? err.message : String(err),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { injected, failed };
|
|
73
|
+
}
|