wasm-idle 1.1.0 → 1.1.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.
Files changed (145) hide show
  1. package/README.md +140 -37
  2. package/dist/playground/assetBridge.js +106 -31
  3. package/dist/playground/assets.d.ts +2 -2
  4. package/dist/playground/c3.js +22 -0
  5. package/dist/playground/c3Memory.d.ts +19 -0
  6. package/dist/playground/c3Memory.js +117 -0
  7. package/dist/playground/cachedClang.js +143 -34
  8. package/dist/playground/clangAssetIntegrity.js +6 -6
  9. package/dist/playground/clangWorkerProtocol.d.ts +12 -1
  10. package/dist/playground/cobol.d.ts +3 -0
  11. package/dist/playground/cobol.js +10 -1
  12. package/dist/playground/commonlispAssets.d.ts +2 -2
  13. package/dist/playground/compiledArtifactCache.d.ts +3 -1
  14. package/dist/playground/compiledArtifactCache.js +31 -5
  15. package/dist/playground/container/ContainerExplorer.svelte +307 -0
  16. package/dist/playground/container/ContainerExplorer.svelte.d.ts +14 -0
  17. package/dist/playground/container/binding.d.ts +26 -0
  18. package/dist/playground/container/binding.js +247 -0
  19. package/dist/playground/container/clang-runtime.d.ts +38 -0
  20. package/dist/playground/container/clang-runtime.js +343 -0
  21. package/dist/playground/container/client.d.ts +15 -0
  22. package/dist/playground/container/client.js +189 -0
  23. package/dist/playground/container/explorer.d.ts +31 -0
  24. package/dist/playground/container/explorer.js +112 -0
  25. package/dist/playground/container/owner.worker.js +390 -0
  26. package/dist/playground/container/protocol.d.ts +73 -0
  27. package/dist/playground/container/protocol.js +28 -0
  28. package/dist/playground/container/release.d.ts +31 -0
  29. package/dist/playground/container/release.js +612 -0
  30. package/dist/playground/container/runtime-adapter.d.ts +73 -0
  31. package/dist/playground/container/runtime-adapter.js +474 -0
  32. package/dist/playground/container/shell-control.d.ts +49 -0
  33. package/dist/playground/container/shell-control.js +303 -0
  34. package/dist/playground/container/workspace.d.ts +8 -0
  35. package/dist/playground/container/workspace.js +29 -0
  36. package/dist/playground/d.d.ts +6 -1
  37. package/dist/playground/d.js +83 -5
  38. package/dist/playground/dotnet.js +6 -8
  39. package/dist/playground/fortran.d.ts +3 -0
  40. package/dist/playground/fortran.js +10 -1
  41. package/dist/playground/go.js +14 -3
  42. package/dist/playground/goDebugInstrumentation.js +184 -54
  43. package/dist/playground/java.js +3 -2
  44. package/dist/playground/javaRuntimeErrors.d.ts +9 -0
  45. package/dist/playground/javaRuntimeErrors.js +60 -0
  46. package/dist/playground/lean4Assets.d.ts +4 -4
  47. package/dist/playground/lfortran.js +10 -0
  48. package/dist/playground/lfortranAssets.d.ts +2 -2
  49. package/dist/playground/nim.js +22 -0
  50. package/dist/playground/objectivec.d.ts +3 -0
  51. package/dist/playground/objectivec.js +11 -2
  52. package/dist/playground/options.d.ts +2 -0
  53. package/dist/playground/python.js +8 -0
  54. package/dist/playground/runtimeAssetCache.d.ts +1 -32
  55. package/dist/playground/runtimeAssetCache.js +1 -193
  56. package/dist/playground/runtimeSession.d.ts +2 -2
  57. package/dist/playground/runtimeSession.js +11 -11
  58. package/dist/playground/rust.d.ts +6 -0
  59. package/dist/playground/rust.js +103 -6
  60. package/dist/playground/staticStdinRing.d.ts +2 -1
  61. package/dist/playground/staticStdinRing.js +21 -10
  62. package/dist/playground/staticWorkerRuntime.d.ts +15 -1
  63. package/dist/playground/staticWorkerRuntime.js +119 -17
  64. package/dist/playground/tinygo.d.ts +3 -0
  65. package/dist/playground/tinygo.js +81 -6
  66. package/dist/playground/v.d.ts +3 -0
  67. package/dist/playground/v.js +10 -1
  68. package/dist/playground/wasmAwkVersion.d.ts +13 -13
  69. package/dist/playground/wasmAwkVersion.js +4 -4
  70. package/dist/playground/wasmBqnVersion.d.ts +2 -2
  71. package/dist/playground/wasmBqnVersion.js +2 -2
  72. package/dist/playground/wasmC3Version.d.ts +2 -2
  73. package/dist/playground/wasmC3Version.js +2 -2
  74. package/dist/playground/wasmClojureScriptVersion.d.ts +2 -2
  75. package/dist/playground/wasmClojureScriptVersion.js +2 -2
  76. package/dist/playground/wasmCommonLispVersion.d.ts +2 -2
  77. package/dist/playground/wasmCommonLispVersion.js +2 -2
  78. package/dist/playground/wasmDIntegrity.d.ts +5 -5
  79. package/dist/playground/wasmDIntegrity.js +5 -5
  80. package/dist/playground/wasmDVersion.d.ts +16 -1
  81. package/dist/playground/wasmDVersion.js +16 -1
  82. package/dist/playground/wasmDebugVersion.d.ts +1 -1
  83. package/dist/playground/wasmDebugVersion.js +1 -1
  84. package/dist/playground/wasmForthVersion.d.ts +2 -2
  85. package/dist/playground/wasmForthVersion.js +2 -2
  86. package/dist/playground/wasmGleamVersion.d.ts +2 -2
  87. package/dist/playground/wasmGleamVersion.js +2 -2
  88. package/dist/playground/wasmGrainVersion.d.ts +2 -2
  89. package/dist/playground/wasmGrainVersion.js +2 -2
  90. package/dist/playground/wasmJVersion.d.ts +2 -2
  91. package/dist/playground/wasmJVersion.js +2 -2
  92. package/dist/playground/wasmJanetVersion.js +2 -2
  93. package/dist/playground/wasmJuliaVersion.js +2 -2
  94. package/dist/playground/wasmLean4Version.d.ts +5 -5
  95. package/dist/playground/wasmLean4Version.js +5 -5
  96. package/dist/playground/wasmLfortranVersion.d.ts +2 -2
  97. package/dist/playground/wasmLfortranVersion.js +2 -2
  98. package/dist/playground/wasmNimVersion.d.ts +9 -9
  99. package/dist/playground/wasmNimVersion.js +6 -6
  100. package/dist/playground/wasmOcamlVersion.d.ts +2 -2
  101. package/dist/playground/wasmOcamlVersion.js +1 -1
  102. package/dist/playground/wasmPascalVersion.js +2 -2
  103. package/dist/playground/wasmPerlVersion.js +2 -2
  104. package/dist/playground/wasmPrologVersion.d.ts +2 -2
  105. package/dist/playground/wasmPrologVersion.js +2 -2
  106. package/dist/playground/wasmReScriptVersion.d.ts +2 -2
  107. package/dist/playground/wasmReScriptVersion.js +2 -2
  108. package/dist/playground/wasmRustVersion.d.ts +8 -8
  109. package/dist/playground/wasmRustVersion.js +7 -7
  110. package/dist/playground/wasmTclVersion.d.ts +4 -4
  111. package/dist/playground/wasmTclVersion.js +2 -2
  112. package/dist/playground/wasmTinyGoVersion.d.ts +14 -14
  113. package/dist/playground/wasmTinyGoVersion.js +14 -14
  114. package/dist/playground/wasmTypeScriptVersion.d.ts +5 -5
  115. package/dist/playground/wasmTypeScriptVersion.js +5 -5
  116. package/dist/playground/worker/cachedClangCompile.js +52 -4
  117. package/dist/playground/worker/cachedClangExecute.js +6 -5
  118. package/dist/playground/worker/clang.js +32 -10
  119. package/dist/playground/worker/cobol.js +2 -2
  120. package/dist/playground/worker/d.js +12 -5
  121. package/dist/playground/worker/fortran.js +2 -2
  122. package/dist/playground/worker/go.js +16 -5
  123. package/dist/playground/worker/haskell.js +10 -7
  124. package/dist/playground/worker/java.js +75 -10
  125. package/dist/playground/worker/lua.js +2 -3
  126. package/dist/playground/worker/php.js +15 -9
  127. package/dist/playground/worker/python.js +46 -9
  128. package/dist/playground/worker/pythonExecution.js +17 -1
  129. package/dist/playground/worker/pythonWorkspaceFiles.d.ts +6 -0
  130. package/dist/playground/worker/pythonWorkspaceFiles.js +26 -0
  131. package/dist/playground/worker/pythonWorkspaceImports.d.ts +4 -0
  132. package/dist/playground/worker/pythonWorkspaceImports.js +129 -0
  133. package/dist/playground/worker/r.js +13 -6
  134. package/dist/playground/worker/rust.js +34 -2
  135. package/dist/playground/worker/sqlite.js +4 -3
  136. package/dist/playground/worker/tinygo.js +11 -3
  137. package/dist/playground/worker/typescript.js +2 -3
  138. package/dist/playground/worker/v.js +2 -2
  139. package/dist/playground/worker/wat.js +2 -3
  140. package/dist/playground/worker/zig.js +20 -6
  141. package/dist/playground/workerSession.d.ts +1 -28
  142. package/dist/playground/workerSession.js +1 -164
  143. package/package.json +20 -13
  144. package/dist/playground/worker/python.runtime.cases.js +0 -374
  145. /package/dist/playground/{worker/python.runtime.cases.d.ts → container/owner.worker.d.ts} +0 -0
@@ -0,0 +1,303 @@
1
+ export const SHELL_CONTROL_COMMAND = 'wasm-idle-shell-control';
2
+ export const SHELL_CONTROL_FUNCTION_ENV = 'BASH_FUNC___wasm_idle_poll%%';
3
+ export const SHELL_CONTROL_ROUTE = `/usr/local/bin/${SHELL_CONTROL_COMMAND}`;
4
+ export const SHELL_CONTROL_MOUNT = '/usr/share/wasm-idle-shell-control';
5
+ export const SHELL_CONTROL_RCFILE = `${SHELL_CONTROL_MOUNT}/rcfile`;
6
+ export const SHELL_CONTROL_REQUEST = '/tmp/.wasm-idle-shell-control-request';
7
+ const SHELL_INPUTRC = '/tmp/.wasm-idle-shell-inputrc';
8
+ const ADAPTER = 'website-shell-control-v1';
9
+ const MAX_GENERATION = 2147483647;
10
+ const encoder = new TextEncoder();
11
+ const abortError = (message) => new DOMException(message, 'AbortError');
12
+ const canonicalDirectory = (path) => path.length <= 4096 &&
13
+ path.startsWith('/') &&
14
+ !path.includes('\0') &&
15
+ (path === '/' ||
16
+ path
17
+ .slice(1)
18
+ .split('/')
19
+ .every((part) => part && part !== '.' && part !== '..'));
20
+ function rcfile(session, languages) {
21
+ return [
22
+ '# Trusted application rcfile. The mailbox is data and is never evaluated.',
23
+ '__wasm_idle_poll() {',
24
+ ' local __wic_version __wic_session __wic_generation __wic_language __wic_extra __wic_oldpwd __wic_fd',
25
+ ` local __wic_expected_session='${session}'`,
26
+ // WASIX dup2 restoration loses stdin's polling rights. Read through a fresh
27
+ // descriptor instead of temporarily replacing the live terminal's fd 0.
28
+ ` [[ -f '${SHELL_CONTROL_REQUEST}' && -r '${SHELL_CONTROL_REQUEST}' ]] || return 0`,
29
+ ` if builtin exec {__wic_fd}< '${SHELL_CONTROL_REQUEST}'; then :; else return 0; fi`,
30
+ ' if IFS=\' \' builtin read -r -n 160 -u "$__wic_fd" __wic_version __wic_session __wic_generation __wic_language __wic_extra; then',
31
+ ' builtin exec {__wic_fd}<&-',
32
+ ' else',
33
+ ' builtin exec {__wic_fd}<&-',
34
+ ' return 0',
35
+ ' fi',
36
+ ' [[ "$__wic_version" == 1 && "$__wic_session" == "$__wic_expected_session" && -z "$__wic_extra" ]] || return 0',
37
+ ' [[ "$__wic_generation" =~ ^[1-9][0-9]{0,9}$ ]] || return 0',
38
+ ` (( __wic_generation <= ${MAX_GENERATION} && __wic_generation > \${__wasm_idle_control_generation:-0} )) || return 0`,
39
+ ` case "$__wic_language" in ${languages.join('|')}) ;; *) return 0 ;; esac`,
40
+ ' __wasm_idle_control_generation=$__wic_generation',
41
+ ' __wic_oldpwd=$PWD',
42
+ ' if builtin cd -P -- "/workspace/$__wic_language"; then',
43
+ ` if '${SHELL_CONTROL_ROUTE}' 1 "$__wic_session" "$__wic_generation" "$__wic_language" ok; then :; else :; fi`,
44
+ ' [[ "$__wic_oldpwd" != "$PWD" ]] && return 124',
45
+ ' else',
46
+ ` if '${SHELL_CONTROL_ROUTE}' 1 "$__wic_session" "$__wic_generation" "$__wic_language" failed; then :; else :; fi`,
47
+ ' fi',
48
+ ' return 0',
49
+ '}',
50
+ // The native primary Readline hook preserves both $? and PIPESTATUS.
51
+ // A PROMPT_COMMAND wrapper cannot restore PIPESTATUS and must not own polling.
52
+ 'readonly -f __wasm_idle_poll',
53
+ ''
54
+ ].join('\n');
55
+ }
56
+ /** Host directory requests are read only at the producer's real primary-prompt boundary. */
57
+ export function createShellControl(options) {
58
+ const languages = [...new Set(options.languages)].sort();
59
+ if (!languages.length || languages.some((language) => !/^[A-Z][A-Z0-9_]{0,63}$/.test(language)))
60
+ throw new Error('Shell control requires a fixed language allowlist');
61
+ const allowed = new Set(languages);
62
+ const session = options.session ??
63
+ Array.from(crypto.getRandomValues(new Uint8Array(16)), (byte) => byte.toString(16).padStart(2, '0')).join('');
64
+ if (!/^[a-f0-9]{32}$/.test(session))
65
+ throw new Error('Invalid shell control session');
66
+ let cwd = options.initialDirectory ?? '/workspace';
67
+ if (cwd !== '/workspace' && !languages.some((language) => cwd === `/workspace/${language}`))
68
+ throw new Error('Invalid initial shell directory');
69
+ let filesystem, disposed = false, generation = 0, appliedGeneration = 0;
70
+ let pending, error = null;
71
+ let lastAcknowledgement;
72
+ const inspect = () => Object.freeze({
73
+ ready: Boolean(filesystem),
74
+ disposed,
75
+ cwd,
76
+ pendingCwd: pending?.cwd ?? null,
77
+ generation,
78
+ appliedGeneration,
79
+ error
80
+ });
81
+ const changed = () => {
82
+ try {
83
+ options.onChange?.(inspect());
84
+ }
85
+ catch {
86
+ /* Observers do not own protocol state. */
87
+ }
88
+ };
89
+ const currentFilesystem = () => {
90
+ if (disposed)
91
+ throw new Error('Shell directory control has been disposed');
92
+ if (!filesystem)
93
+ throw new Error('Shell directory control has not been set up');
94
+ return filesystem;
95
+ };
96
+ const ensureDirectory = (fs, path) => {
97
+ if (fs.lstat('/workspace').type !== 'directory' ||
98
+ fs.realpath('/workspace') !== '/workspace')
99
+ throw new Error('Workspace root is not a directory');
100
+ try {
101
+ if (fs.lstat(path).type !== 'directory' || fs.realpath(path) !== path)
102
+ throw new Error('Shell directory must be an actual project directory');
103
+ }
104
+ catch (reason) {
105
+ if (reason.code !== 'ENOENT')
106
+ throw reason;
107
+ fs.mkdir(path);
108
+ }
109
+ };
110
+ const writeRequest = (fs, value) => {
111
+ if (fs.lstat('/tmp').type !== 'directory' || fs.realpath('/tmp') !== '/tmp')
112
+ throw new Error('Shell control needs the private temporary mount');
113
+ try {
114
+ if (fs.lstat(SHELL_CONTROL_REQUEST).type !== 'file')
115
+ throw new Error('Shell control mailbox must be a regular file');
116
+ }
117
+ catch (reason) {
118
+ if (reason.code !== 'ENOENT')
119
+ throw reason;
120
+ }
121
+ fs.writeFile(SHELL_CONTROL_REQUEST, encoder.encode(value), { mode: 0o600 });
122
+ };
123
+ const acknowledge = (args, actualCwd) => {
124
+ if (disposed ||
125
+ !filesystem ||
126
+ args.length !== 5 ||
127
+ args[0] !== '1' ||
128
+ args[1] !== session ||
129
+ !/^[1-9][0-9]{0,9}$/.test(args[2]) ||
130
+ !allowed.has(args[3]) ||
131
+ !['ok', 'failed'].includes(args[4]) ||
132
+ !canonicalDirectory(actualCwd))
133
+ return 126;
134
+ const received = Number(args[2]), language = args[3], verdict = args[4];
135
+ if (verdict === 'ok' && actualCwd !== `/workspace/${language}`)
136
+ return 126;
137
+ if (received > MAX_GENERATION || received > generation)
138
+ return 126;
139
+ if (received < generation)
140
+ return 0; // A superseded completion cannot publish the new request.
141
+ if (!pending)
142
+ return lastAcknowledgement?.generation === received &&
143
+ lastAcknowledgement.language === language &&
144
+ lastAcknowledgement.cwd === actualCwd &&
145
+ lastAcknowledgement.verdict === verdict
146
+ ? 0
147
+ : 126;
148
+ if (pending.language !== language || (verdict === 'ok' && actualCwd !== pending.cwd))
149
+ return 126;
150
+ const completed = pending;
151
+ pending = undefined;
152
+ cwd = actualCwd;
153
+ lastAcknowledgement = { generation: received, language, cwd: actualCwd, verdict };
154
+ if (verdict === 'ok') {
155
+ appliedGeneration = received;
156
+ error = null;
157
+ completed.resolve();
158
+ }
159
+ else {
160
+ error = `Bash could not enter ${completed.cwd}`;
161
+ completed.reject(new Error(error));
162
+ }
163
+ changed();
164
+ return 0;
165
+ };
166
+ const adapter = {
167
+ id: ADAPTER,
168
+ filesystem: 'live',
169
+ async start(request) {
170
+ if (request.fs !== currentFilesystem() ||
171
+ request.invocation.command !== SHELL_CONTROL_COMMAND)
172
+ throw new Error('Shell control command belongs to a different container');
173
+ const emptyOutput = () => new ReadableStream({
174
+ start(controller) {
175
+ controller.close();
176
+ }
177
+ });
178
+ return {
179
+ stdin: new WritableStream({
180
+ write(bytes) {
181
+ if (bytes.length)
182
+ throw new Error('Shell control does not consume program input');
183
+ }
184
+ }),
185
+ stdout: emptyOutput(),
186
+ stderr: emptyOutput(),
187
+ async run() {
188
+ return {
189
+ code: request.signal.aborted
190
+ ? 143
191
+ : acknowledge(request.invocation.args, request.invocation.cwd)
192
+ };
193
+ },
194
+ async dispose() { }
195
+ };
196
+ }
197
+ };
198
+ const commands = Object.freeze({
199
+ [SHELL_CONTROL_COMMAND]: Object.freeze({
200
+ adapter: ADAPTER,
201
+ bytes: new Uint8Array(),
202
+ argv0: SHELL_CONTROL_COMMAND
203
+ })
204
+ });
205
+ const configuration = Object.freeze({
206
+ mountPath: SHELL_CONTROL_MOUNT,
207
+ rcfilePath: SHELL_CONTROL_RCFILE,
208
+ requestPath: SHELL_CONTROL_REQUEST,
209
+ // The pinned WASIX host requests inherited write access even for O_RDONLY.
210
+ // Standard Bash function import bootstraps the trusted body without opening
211
+ // a readonly file; the opt-in producer makes this one imported function readonly.
212
+ args: Object.freeze(['--noprofile', '--norc', '-i']),
213
+ env: Object.freeze({
214
+ WASM_IDLE_SHELL_CONTROL: '1',
215
+ INPUTRC: SHELL_INPUTRC,
216
+ [SHELL_CONTROL_FUNCTION_ENV]: `() ${rcfile(session, languages).match(/__wasm_idle_poll\(\) (\{[\s\S]*?\n\})/)[1]}`
217
+ })
218
+ });
219
+ return Object.freeze({
220
+ adapter,
221
+ commands,
222
+ route: Object.freeze({ path: SHELL_CONTROL_ROUTE, command: SHELL_CONTROL_COMMAND }),
223
+ setup(fs) {
224
+ if (disposed)
225
+ throw new Error('Shell directory control has been disposed');
226
+ if (filesystem) {
227
+ if (filesystem !== fs)
228
+ throw new Error('Shell control cannot share containers');
229
+ return configuration;
230
+ }
231
+ ensureDirectory(fs, cwd);
232
+ writeRequest(fs, `1 ${session} 0 -\n`);
233
+ // The pinned C locale otherwise interprets UTF-8 input as Meta keys.
234
+ // Keep this on the writable mount: WASIX overrequests rights on open.
235
+ fs.writeFile(SHELL_INPUTRC, encoder.encode('set input-meta on\nset convert-meta off\nset output-meta on\n'), { mode: 0o600 });
236
+ fs.mountReadonly(SHELL_CONTROL_MOUNT, [
237
+ {
238
+ path: 'rcfile',
239
+ type: 'file',
240
+ mode: 0o444,
241
+ data: encoder.encode(rcfile(session, languages))
242
+ }
243
+ ]);
244
+ filesystem = fs;
245
+ changed();
246
+ return configuration;
247
+ },
248
+ requestDirectory(language) {
249
+ try {
250
+ const fs = currentFilesystem();
251
+ if (!allowed.has(language))
252
+ throw new Error('Unknown shell project directory');
253
+ if (pending?.language === language)
254
+ return pending.promise;
255
+ if (generation === MAX_GENERATION)
256
+ throw new Error('Shell directory generation exhausted');
257
+ const target = `/workspace/${language}`, nextGeneration = generation + 1;
258
+ ensureDirectory(fs, target);
259
+ writeRequest(fs, `1 ${session} ${nextGeneration} ${language}\n`);
260
+ pending?.reject(abortError('Shell directory request was superseded'));
261
+ let resolve, reject;
262
+ const promise = new Promise((yes, no) => {
263
+ resolve = yes;
264
+ reject = no;
265
+ });
266
+ pending = { language, cwd: target, promise, resolve, reject };
267
+ generation = nextGeneration;
268
+ error = null;
269
+ changed();
270
+ return promise;
271
+ }
272
+ catch (reason) {
273
+ return Promise.reject(reason);
274
+ }
275
+ },
276
+ onInvocation(invocation) {
277
+ return invocation.path === SHELL_CONTROL_ROUTE
278
+ ? acknowledge(invocation.argv.slice(1), invocation.cwd)
279
+ : undefined;
280
+ },
281
+ inspect,
282
+ dispose() {
283
+ if (disposed)
284
+ return;
285
+ disposed = true;
286
+ pending?.reject(abortError('Shell directory control was disposed'));
287
+ pending = undefined;
288
+ if (filesystem) {
289
+ try {
290
+ if (filesystem.lstat(SHELL_CONTROL_REQUEST).type === 'file' &&
291
+ new TextDecoder()
292
+ .decode(filesystem.readFile(SHELL_CONTROL_REQUEST))
293
+ .startsWith(`1 ${session} `))
294
+ filesystem.unlink(SHELL_CONTROL_REQUEST);
295
+ }
296
+ catch {
297
+ /* The guest may already have removed the temporary mailbox. */
298
+ }
299
+ }
300
+ changed();
301
+ }
302
+ });
303
+ }
@@ -0,0 +1,8 @@
1
+ import type { WorkspaceFile } from '../../../routes/workspaceCodec';
2
+ /** Reconcile independent terminal edits without discarding an unpublished editor revision. */
3
+ export declare function mergeContainerWorkspace(local: readonly WorkspaceFile[], baseline: readonly WorkspaceFile[], remote: readonly WorkspaceFile[], options?: {
4
+ acceptRemoteConflicts?: boolean;
5
+ }): {
6
+ files: WorkspaceFile[];
7
+ conflicts: string[];
8
+ };
@@ -0,0 +1,29 @@
1
+ const same = (a, b) => a === b ||
2
+ (!!a && !!b && a.content === b.content && (a.encoding ?? 'utf-8') === (b.encoding ?? 'utf-8'));
3
+ /** Reconcile independent terminal edits without discarding an unpublished editor revision. */
4
+ export function mergeContainerWorkspace(local, baseline, remote, options = {}) {
5
+ const localByPath = new Map(local.map((file) => [file.path, file]));
6
+ const baseByPath = new Map(baseline.map((file) => [file.path, file]));
7
+ const remoteByPath = new Map(remote.map((file) => [file.path, file]));
8
+ const conflicts = [];
9
+ const files = [];
10
+ for (const path of new Set([
11
+ ...localByPath.keys(),
12
+ ...baseByPath.keys(),
13
+ ...remoteByPath.keys()
14
+ ])) {
15
+ const here = localByPath.get(path), before = baseByPath.get(path), there = remoteByPath.get(path);
16
+ let selected;
17
+ if (same(here, before))
18
+ selected = there;
19
+ else if (same(there, before) || same(here, there))
20
+ selected = here;
21
+ else {
22
+ conflicts.push(path);
23
+ selected = options.acceptRemoteConflicts ? there : here;
24
+ }
25
+ if (selected)
26
+ files.push({ ...selected });
27
+ }
28
+ return { files, conflicts };
29
+ }
@@ -1,4 +1,5 @@
1
1
  import { type PlaygroundRuntimeAssets, type ResolvedDRuntimeAssetConfig } from './assets';
2
+ import { RuntimeAssetCache } from '@wasm-idle/core';
2
3
  import { type CompilerDiagnostic, type SandboxExecutionOptions } from './options';
3
4
  import type { Sandbox, SandboxProgress } from './sandbox';
4
5
  declare class D implements Sandbox {
@@ -19,6 +20,8 @@ declare class D implements Sandbox {
19
20
  pendingEof: boolean;
20
21
  private activeOperation;
21
22
  private disposed;
23
+ private runtimeAssetCache;
24
+ private ownsRuntimeAssetCache;
22
25
  private disposePromise;
23
26
  private readonly disposeCancellation;
24
27
  private readonly workerSession;
@@ -37,8 +40,10 @@ declare class D implements Sandbox {
37
40
  write(input: string): void;
38
41
  eof(): void;
39
42
  private flushPendingInput;
40
- run(code: string, prepare: boolean, _log?: boolean, _prog?: SandboxProgress, args?: string[], options?: SandboxExecutionOptions): Promise<boolean | string>;
43
+ run(code: string, prepare: boolean, log?: boolean, progress?: SandboxProgress, args?: string[], options?: SandboxExecutionOptions): Promise<boolean | string>;
44
+ private runLoaded;
41
45
  private cancelOperation;
46
+ setRuntimeAssetCache(cache: RuntimeAssetCache): void;
42
47
  kill(): void;
43
48
  terminate(reason?: unknown): void;
44
49
  clear(): Promise<void>;
@@ -1,10 +1,12 @@
1
1
  import { resolveDRuntimeAssetConfig } from './assets';
2
- import { BusyError, CancelledError, createRuntimeAssetsKey, DEFAULT_WORKSPACE_LIMITS, DiagnosticLimitError, OutputLimitError, RuntimeConfigurationError, TimeoutError, resolveExecutionLimits, validateExecutionWorkspace } from '@wasm-idle/core';
2
+ import { BusyError, CancelledError, createRuntimeAssetsKey, createRuntimeWasmModuleService, DEFAULT_WORKSPACE_LIMITS, DiagnosticLimitError, OutputLimitError, RuntimeConfigurationError, RuntimeAssetCache, TimeoutError, resolveExecutionLimits, validateExecutionWorkspace } from '@wasm-idle/core';
3
3
  import { resolveSandboxExecutionArgs } from './options';
4
4
  import { flushBufferedEof, flushQueuedStdin, resetBufferedStdin } from './stdinBuffer';
5
5
  import { createWasmIdleSharedBuffer } from './sharedBuffer';
6
6
  import { WorkerSession } from './workerSession';
7
7
  import { reportWorkerInputReady, reportWorkerProgress } from './workerProgress';
8
+ import { WASM_D_COMPILER_PROFILE } from './wasmDVersion';
9
+ import { WASM_D_OUTER_ASSET_RECEIPTS } from './wasmDIntegrity';
8
10
  const OUTPUT_ENCODER = new TextEncoder();
9
11
  const abortReason = (signal, phase) => signal.reason !== undefined
10
12
  ? signal.reason
@@ -27,6 +29,8 @@ class D {
27
29
  pendingEof = false;
28
30
  activeOperation = null;
29
31
  disposed = false;
32
+ runtimeAssetCache = new RuntimeAssetCache();
33
+ ownsRuntimeAssetCache = true;
30
34
  disposePromise = null;
31
35
  disposeCancellation = new CancelledError('D sandbox disposed', {
32
36
  phase: 'dispose',
@@ -382,7 +386,19 @@ class D {
382
386
  this.waitingForInput = false;
383
387
  }
384
388
  }
385
- run(code, prepare, _log = true, _prog, args = [], options = {}) {
389
+ async run(code, prepare, log = true, progress, args = [], options = {}) {
390
+ if (!this.worker && this.moduleUrl && this.outerIntegrity && !this.disposed) {
391
+ await this.load({
392
+ d: {
393
+ moduleUrl: this.moduleUrl,
394
+ manifestUrl: this.manifestUrl,
395
+ integrity: this.outerIntegrity
396
+ }
397
+ }, '', log, [], options, progress);
398
+ }
399
+ return this.runLoaded(code, prepare, log, progress, args, options);
400
+ }
401
+ runLoaded(code, prepare, _log = true, _prog, args = [], options = {}) {
386
402
  let activeOperation;
387
403
  try {
388
404
  activeOperation = this.beginOperation('execute');
@@ -467,6 +483,12 @@ class D {
467
483
  catch {
468
484
  // Handler cleanup must not replace the operation result.
469
485
  }
486
+ // Only immutable compiler modules survive an execution. Retire the worker, its
487
+ // Emscripten heaps, filesystem and compiled guest before the next run. A
488
+ // prepare-only call has executed no guest; keep its compiled artifact for
489
+ // the following execution instead of compiling the same source twice.
490
+ if (!prepare)
491
+ this.workerSession.reset();
470
492
  return true;
471
493
  };
472
494
  const failRun = (error, disposeWorker = false) => {
@@ -549,15 +571,58 @@ class D {
549
571
  return;
550
572
  this.begin = Date.now();
551
573
  try {
552
- worker.postMessage({
574
+ const modulePorts = {};
575
+ const profile = WASM_D_COMPILER_PROFILE;
576
+ // Custom runtime bundles keep their independent compiler path. A worker
577
+ // cannot choose the host's receipt, loader profile or authorization key.
578
+ if (typeof MessageChannel === 'function' &&
579
+ this.outerIntegrity?.['runtime/runtime-manifest.v1.json'].sha256 ===
580
+ profile.manifestSha256 &&
581
+ this.outerIntegrity?.['index.js'].sha256 ===
582
+ WASM_D_OUTER_ASSET_RECEIPTS['index.js'].sha256) {
583
+ for (const kind of ['ldc2', 'lld']) {
584
+ const service = createRuntimeWasmModuleService({
585
+ cache: this.runtimeAssetCache,
586
+ receipt: profile[kind],
587
+ identity: JSON.stringify([
588
+ 'wasm-d-verified-compiler-v1',
589
+ this.activeDAssetsKey,
590
+ profile.version,
591
+ profile.manifestSha256,
592
+ kind,
593
+ ...(kind === 'lld'
594
+ ? [
595
+ profile.lldProfile,
596
+ profile.lldLoaderSha256,
597
+ profile.lldDataSha256
598
+ ]
599
+ : [])
600
+ ]),
601
+ compileKey: kind === 'lld'
602
+ ? 'emscripten-lld16-instantiate-v1'
603
+ : 'ldc2-wasi-preview1-v1',
604
+ maxAssetBytes: limits.maxAssetBytes,
605
+ signal
606
+ });
607
+ activeOperation.cleanups.push(() => service.close());
608
+ modulePorts[kind] = service.port;
609
+ }
610
+ }
611
+ const message = {
553
612
  code,
554
613
  prepare,
555
614
  buffer: this.buffer,
556
615
  args: programArgs,
557
616
  stdin,
558
617
  fileName: workspace.activePath,
559
- log: _log
560
- });
618
+ log: _log,
619
+ ...(modulePorts.ldc2 ? { compilerModulePorts: modulePorts } : {})
620
+ };
621
+ const transfers = Object.values(modulePorts);
622
+ if (transfers.length)
623
+ worker.postMessage(message, transfers);
624
+ else
625
+ worker.postMessage(message);
561
626
  }
562
627
  catch (error) {
563
628
  failRun(error, true);
@@ -585,6 +650,17 @@ class D {
585
650
  this.workerSession.terminate(reason);
586
651
  this.cleanupOperation(operation);
587
652
  }
653
+ setRuntimeAssetCache(cache) {
654
+ if (cache === this.runtimeAssetCache)
655
+ return;
656
+ if (this.worker || this.activeOperation || this.disposed) {
657
+ throw new Error('Cannot replace the D runtime asset cache while its worker is active or disposed');
658
+ }
659
+ if (this.ownsRuntimeAssetCache)
660
+ this.runtimeAssetCache.dispose();
661
+ this.runtimeAssetCache = cache;
662
+ this.ownsRuntimeAssetCache = false;
663
+ }
588
664
  kill() {
589
665
  this.terminate();
590
666
  }
@@ -620,6 +696,8 @@ class D {
620
696
  if (this.disposePromise)
621
697
  return this.disposePromise;
622
698
  this.disposed = true;
699
+ if (this.ownsRuntimeAssetCache)
700
+ this.runtimeAssetCache.dispose();
623
701
  this.disposePromise = Promise.resolve();
624
702
  const activeOperation = this.activeOperation;
625
703
  delete this.worker;
@@ -495,15 +495,13 @@ class Dotnet {
495
495
  const hasExplicitStdin = !prepare && explicitStdin !== undefined;
496
496
  if (runUid !== this.uid)
497
497
  return '';
498
- if (!prepare &&
499
- !hasExplicitStdin &&
500
- this.pendingInput.length === 0 &&
501
- !this.pendingEof &&
502
- readsConsoleStdin(code)) {
498
+ if (!prepare && !hasExplicitStdin && !this.pendingEof && readsConsoleStdin(code)) {
503
499
  reportWorkerInputReady(progress, `${this.languageLabel} runtime ready for input`);
504
- if (runUid !== this.uid)
505
- return '';
506
- await new Promise((resolve) => this.stdinWaiters.push(resolve));
500
+ // The program receives stdin as one string, so wait for EOF instead of
501
+ // starting with only the input written before the first chunk arrived.
502
+ while (runUid === this.uid && !this.pendingEof) {
503
+ await new Promise((resolve) => this.stdinWaiters.push(resolve));
504
+ }
507
505
  }
508
506
  if (runUid !== this.uid)
509
507
  return '';
@@ -1,4 +1,5 @@
1
1
  import { WorkerAssetBridge } from './assetBridge';
2
+ import type { RuntimeAssetCache } from './runtimeAssetCache';
2
3
  import { type PlaygroundRuntimeAssets } from './assets';
3
4
  import type { SandboxExecutionOptions } from './options';
4
5
  import type { Sandbox, SandboxProgress } from './sandbox';
@@ -18,6 +19,7 @@ declare class Fortran implements Sandbox {
18
19
  assetBridge: WorkerAssetBridge | null;
19
20
  activeFortranAssetsKey: string;
20
21
  private activeOperation;
22
+ private runtimeAssetCache?;
21
23
  private preserveOperationOnWorkerDispose;
22
24
  private disposed;
23
25
  private disposePromise;
@@ -25,6 +27,7 @@ declare class Fortran implements Sandbox {
25
27
  private readonly workerSession;
26
28
  private disposedConfigurationError;
27
29
  private requireOperationIdle;
30
+ setRuntimeAssetCache(cache: RuntimeAssetCache): void;
28
31
  private beginOperation;
29
32
  private isOperationActive;
30
33
  private releaseOperation;
@@ -37,6 +37,7 @@ class Fortran {
37
37
  assetBridge = null;
38
38
  activeFortranAssetsKey = '';
39
39
  activeOperation = null;
40
+ runtimeAssetCache;
40
41
  preserveOperationOnWorkerDispose = false;
41
42
  disposed = false;
42
43
  disposePromise = null;
@@ -88,6 +89,14 @@ class Fortran {
88
89
  });
89
90
  }
90
91
  }
92
+ setRuntimeAssetCache(cache) {
93
+ if (this.runtimeAssetCache === cache)
94
+ return;
95
+ if (this.disposed || this.worker || this.activeOperation) {
96
+ throw new RuntimeConfigurationError('Cannot replace an active Fortran compiler cache');
97
+ }
98
+ this.runtimeAssetCache = cache;
99
+ }
91
100
  beginOperation(phase) {
92
101
  this.requireOperationIdle();
93
102
  const operation = {
@@ -287,7 +296,7 @@ class Fortran {
287
296
  }
288
297
  let assetBridge;
289
298
  try {
290
- assetBridge = new WorkerAssetBridge(worker, 'clang', clangAssets, progress, limits.maxAssetBytes);
299
+ assetBridge = new WorkerAssetBridge(worker, 'clang', clangAssets, progress, limits.maxAssetBytes, false, this.runtimeAssetCache);
291
300
  assetBridge.setExecutionPersistentCache(options.persistentCache);
292
301
  }
293
302
  catch (error) {
@@ -328,7 +328,7 @@ class Go {
328
328
  worker.onmessage = null;
329
329
  return;
330
330
  }
331
- const { output, results, error, buffer, diagnostic, progress, debugEvent, phase } = event.data;
331
+ const { output, results, error, fatal, buffer, diagnostic, progress, debugEvent, phase } = event.data;
332
332
  if (phase === 'compile' || phase === 'execute') {
333
333
  if (phase !== activePhase)
334
334
  armTimeout(phase);
@@ -342,8 +342,12 @@ class Go {
342
342
  reportWorkerProgress(_prog, progress);
343
343
  if (output)
344
344
  this.output(output);
345
+ // The worker compiles only the active source, which the compiler names main.go.
345
346
  if (diagnostic)
346
- this.oncompilerdiagnostic?.(diagnostic);
347
+ this.oncompilerdiagnostic?.(options.activePath &&
348
+ /(?:^|[\\/])main\.go$/u.test(diagnostic.fileName ?? '')
349
+ ? { ...diagnostic, fileName: options.activePath }
350
+ : diagnostic);
347
351
  if (debugEvent)
348
352
  this.ondebug?.(debugEvent);
349
353
  if (results) {
@@ -367,7 +371,14 @@ class Go {
367
371
  this.waitingForInput = false;
368
372
  this.pendingEof = false;
369
373
  this.workerSession.complete(operation);
370
- this.ondebug?.({ type: 'stop' });
374
+ if (fatal === true && this.worker === worker) {
375
+ // The worker abandoned a program that may still be running; terminating
376
+ // it stops that program, and disposal reports the debug stop.
377
+ this.workerSession.reset();
378
+ }
379
+ else {
380
+ this.ondebug?.({ type: 'stop' });
381
+ }
371
382
  reject(error);
372
383
  }
373
384
  };