@playcraft/cli 0.1.71 → 0.1.73

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.
@@ -1,14 +1,14 @@
1
- import { hasPendingLevelEditorTransaction, assertNoPendingLevelEditorTransaction } from '../workspaces/level-editor-atomic.js';
1
+ import { hasPendingLevelEditorTransaction, assertNoPendingLevelEditorTransaction, } from '../workspaces/level-editor-atomic.js';
2
2
  import { readObservedFile, withObservedFile } from '../workspaces/revisions.js';
3
3
  import { observeWorkspaceDirectories } from '../workspaces/directory-revisions.js';
4
- import { registerWorkspace, revisionScope, sessionWorkspaceRoot, workspaceRoot, WorkspaceContextError } from '../workspaces/registry.js';
4
+ import { registerWorkspace, revisionScope, sessionWorkspaceRoot, workspaceRoot, WorkspaceContextError, } from '../workspaces/registry.js';
5
5
  import { constants } from 'node:fs';
6
6
  import { access as accessDirectory, stat } from 'node:fs/promises';
7
7
  import { workspaceRegistrationRequestSchema, workspaceFileSearchQuerySchema, HTTP_STATUS_BY_ERROR, assertLeaseFence, buildHandshakeDocument, capability, findUntrustedRuntimeFields, gitFileRefSchema, sourceAckSchema, operationAcceptRequestSchema, operationLogSchema, operationKindSchema, operationScopeKey, productionSessionAttemptId, takesWorkspaceDirectory, nativeHistoryReadSchema, runtimeError, runtimeConnectivityDocumentSchema, unavailableRuntimeCapabilities, workspaceUiResultSchema, workspaceRemixReferenceImportRequestSchema, WORKSPACE_REMIX_REFERENCE_BODY_MAX_BYTES, WORKSPACE_ASSET_LIBRARY_IMPORT_BODY_MAX_BYTES, workspaceSessionRestoreRequestSchema, workspaceSessionRestoreResponseSchema, deliveryValidatePayloadSchema, deliveryBuildListQuerySchema, deliveryBuildListSchema, deliveryBuildDetailSchema, deliveryBuildLogSchema, workspaceDirectoryNameSchema, workspaceDirectoryRegistrationSchema, attachmentIdsSchema, } from '@playcraft/common/cloud-workspace/runtime';
8
8
  import { controlTokenMatches, extractControlToken } from './auth.js';
9
9
  import { applyRuntimeEndpointPatch } from './endpoints.js';
10
10
  import { wakeSourceEventUploader } from './source-uploader.js';
11
- import { InvalidWorkspacePathError, } from '../workspaces/files.js';
11
+ import { InvalidWorkspacePathError } from '../workspaces/files.js';
12
12
  import { GitWorkspaceError, readWorkspaceGitDiff, readWorkspaceGitFile, readWorkspaceGitGraph, readWorkspaceGitStatus, } from '../workspaces/git.js';
13
13
  import { readWorkspaceParameters } from '../workspaces/parameters.js';
14
14
  import { listParameterAgentTasks } from '../workspaces/parameter-agent.js';
@@ -20,29 +20,29 @@ import { readDependencies } from '../workspaces/dependencies.js';
20
20
  import { permissionDriftFromError } from '../workspaces/workspace-shares.js';
21
21
  import { desktopHost, desktopRoute } from './desktop-host.js';
22
22
  import { ensureCliCredentials } from '../workspaces/cli-credentials.js';
23
- import { readAttachmentUpload, uploadWorkspaceAttachment, uploadWorkspaceVideoAttachment } from '../workspaces/attachments.js';
23
+ import { readAttachmentUpload, uploadWorkspaceAttachment, uploadWorkspaceVideoAttachment, } from '../workspaces/attachments.js';
24
24
  import { serveWorkspaceAttachment } from '../workspaces/attachment-content.js';
25
25
  import { serveWorkspaceFileContent } from '../workspaces/file-content.js';
26
26
  import { validateDeliveryArtifact, DeliveryValidationError } from '../workspaces/delivery.js';
27
27
  import { DeliveryStore } from '../workspaces/delivery-store.js';
28
- import { importWorkspaceLibraryAttachment, listWorkspaceLibraryAttachments, listWorkspaceLibraryImportReceipts, readLibraryAttachmentUpload } from '../workspaces/library-attachments.js';
29
- import { beginWorkspaceDirectoryAttachment, uploadWorkspaceDirectoryFile, completeWorkspaceDirectoryAttachment, cancelWorkspaceDirectoryAttachment } from '../workspaces/directory-attachments.js';
28
+ import { importWorkspaceLibraryAttachment, listWorkspaceLibraryAttachments, listWorkspaceLibraryImportReceipts, readLibraryAttachmentUpload, } from '../workspaces/library-attachments.js';
29
+ import { beginWorkspaceDirectoryAttachment, uploadWorkspaceDirectoryFile, completeWorkspaceDirectoryAttachment, cancelWorkspaceDirectoryAttachment, } from '../workspaces/directory-attachments.js';
30
30
  import { readWorkspaceSkills, readWorkspaceSkillSnapshot } from '../workspaces/skills.js';
31
- import { listWorkspaceRemixReferences, RemixReferenceError } from '../workspaces/remix-references.js';
31
+ import { listWorkspaceRemixReferences, RemixReferenceError, } from '../workspaces/remix-references.js';
32
32
  import { loadWorkspaceSkillArtifact } from '../workspaces/skill-artifact-loader.js';
33
33
  import { join } from 'node:path';
34
34
  import { terminalHost } from './terminal-host.js';
35
35
  import { batchDirectoryHeld } from '../batch/occupancy.js';
36
36
  import { dispatchBatchRoute } from '../batch/routes.js';
37
- import { cleanupWorkspaceDirectories, syncWorkspaceDirectories } from '../workspaces/directory-maintenance.js';
38
- import { registerWorkspaceDirectory, restoreWorkspaceDirectory } from '../workspaces/directories.js';
37
+ import { cleanupWorkspaceDirectories, syncWorkspaceDirectories, } from '../workspaces/directory-maintenance.js';
38
+ import { registerWorkspaceDirectory, restoreWorkspaceDirectory, } from '../workspaces/directories.js';
39
39
  import { readWorkspaceDirectoryStats } from '../workspaces/directory-stats.js';
40
40
  import { probeRuntimeConnectivity } from './connectivity.js';
41
41
  import { refreshWorkspaceArtifacts } from '../workspaces/artifact-events.js';
42
42
  import { WorkspaceArtifactStore } from '../workspaces/artifacts.js';
43
43
  import { OperationLogStore } from '../workspaces/operation-log.js';
44
44
  import { acceptOperationCore, observeLevelEditorTransaction } from './accept-operation.js';
45
- import { workspaceArtifactContentQuerySchema } from '@playcraft/common/cloud-workspace/runtime';
45
+ import { workspaceArtifactContentQuerySchema, workspaceBindingIdentity } from '@playcraft/common/cloud-workspace/runtime';
46
46
  import { searchWorkspaceFiles } from '../workspaces/files-search.js';
47
47
  import { serveWorkspaceArchive } from '../workspaces/archive.js';
48
48
  import { verifyRuntimeAssetProvenance } from '../workspaces/asset-provenance-verifier.js';
@@ -52,7 +52,7 @@ import { serveSessionImage } from '../workspaces/session-image.js';
52
52
  import { restoreNativeSession, SessionRestoreError } from './session-restore.js';
53
53
  import { handleAssetTransferRequest } from './asset-transfers.js';
54
54
  import { readWorktreeSource, registerChatWorktree } from '../workspaces/worktrees.js';
55
- import { withDirectoryWrite } from './directory-access.js';
55
+ import { directoryBusyCode, withDirectoryWrite } from './directory-access.js';
56
56
  import { readProceduralModelCandidates } from '../workspaces/procedural-3d/read-candidates.js';
57
57
  import { ModelPreviewStore } from '../workspaces/procedural-3d/model-preview-store.js';
58
58
  import { readModelPreparation } from '../workspaces/model-preparation.js';
@@ -61,23 +61,40 @@ import { handleAudioAssetRequest } from './audio-assets.js';
61
61
  export async function handleRuntimeRequest(state, request, response) {
62
62
  const url = new URL(request.url ?? '/', 'http://127.0.0.1');
63
63
  const workspaceId = url.searchParams.get('workspaceId');
64
- const protectedRead = ['/runtime/level-editor', '/runtime/parameters', '/runtime/file', '/runtime/files', '/runtime/files/search', '/runtime/file-content', '/runtime/archive', '/runtime/git-bundle', '/runtime/git/status', '/runtime/git/diff', '/runtime/audio/assets', '/runtime/audio/content'].includes(url.pathname) && ['GET', 'HEAD'].includes(request.method ?? 'GET');
64
+ const protectedRead = [
65
+ '/runtime/level-editor',
66
+ '/runtime/parameters',
67
+ '/runtime/file',
68
+ '/runtime/files',
69
+ '/runtime/files/search',
70
+ '/runtime/file-content',
71
+ '/runtime/archive',
72
+ '/runtime/git-bundle',
73
+ '/runtime/git/status',
74
+ '/runtime/git/diff',
75
+ '/runtime/audio/assets',
76
+ '/runtime/audio/content',
77
+ ].includes(url.pathname) && ['GET', 'HEAD'].includes(request.method ?? 'GET');
65
78
  if (authorizeRuntime(state, request) === 'control' && protectedRead) {
79
+ const directoryName = url.searchParams.get('directoryName') ?? 'basic';
66
80
  try {
67
- const directoryName = url.searchParams.get('directoryName') ?? 'basic';
68
81
  const readContext = { state, workspaceId: workspaceId ?? undefined, directoryName };
69
82
  const root = await workspaceRoot(state, readContext.workspaceId, directoryName);
70
83
  if (await hasPendingLevelEditorTransaction(root, readContext)) {
71
84
  // A retained writer owns the exclusive lease. Reconcile that exact operation before admitting a reader.
72
85
  for (const { operation } of state.journal.listOperations())
73
- if (operation.kind === 'level-editor.transaction' && (state.config.workspaceMode !== 'multi' || operation.workspaceId === readContext.workspaceId) && (operation.directoryName ?? 'basic') === directoryName)
86
+ if (operation.kind === 'level-editor.transaction' &&
87
+ (state.config.workspaceMode !== 'multi' ||
88
+ operation.workspaceId === readContext.workspaceId) &&
89
+ (operation.directoryName ?? 'basic') === directoryName)
74
90
  await observeLevelEditorTransaction(state, operation);
75
91
  }
76
92
  const recovering = await hasPendingLevelEditorTransaction(root, readContext);
77
93
  return await withDirectoryWrite(state, { ...readContext, mode: recovering ? 'exclusive' : 'shared', wait: false }, async () => {
78
94
  if (recovering) {
79
95
  const snapshot = await readWorkspaceLevelEditor(root, state.journal.fileRevisions(revisionScope(state, readContext.workspaceId, directoryName)), readContext);
80
- if (snapshot.status === 'invalid' && snapshot.diagnostics.some(item => /recovery|history/.test(item.code)))
96
+ if (snapshot.status === 'invalid' &&
97
+ snapshot.diagnostics.some((item) => /recovery|history/.test(item.code)))
81
98
  throw new Error(snapshot.diagnostics[0].code);
82
99
  }
83
100
  await assertNoPendingLevelEditorTransaction(root, readContext);
@@ -85,26 +102,49 @@ export async function handleRuntimeRequest(state, request, response) {
85
102
  });
86
103
  }
87
104
  catch (error) {
88
- if (!response.headersSent)
89
- writeJson(response, 409, { error: error instanceof Error ? error.message : 'level_editor_recovery_required' });
105
+ if (!response.headersSent) {
106
+ const busy = error instanceof Error && error.message === 'directory_busy';
107
+ writeJson(response, 409, {
108
+ error: busy
109
+ ? await directoryBusyCode(state, workspaceId ?? undefined, directoryName)
110
+ : error instanceof Error
111
+ ? error.message
112
+ : 'level_editor_recovery_required',
113
+ });
114
+ }
90
115
  return;
91
116
  }
92
117
  }
93
118
  // Body-scoped operations, members and terminal control take their own lifetime-aware leases.
94
- const ownLease = url.pathname.startsWith('/runtime/operations') || url.pathname.startsWith('/runtime/terminals/') ||
119
+ const ownLease = url.pathname.startsWith('/runtime/operations') ||
120
+ url.pathname.startsWith('/runtime/terminals/') ||
95
121
  url.pathname === '/runtime/audio/export' ||
96
- url.pathname.startsWith('/runtime/batch-') || /^\/runtime\/sessions\/[^/]+\/ui-results$/.test(url.pathname) ||
122
+ url.pathname.startsWith('/runtime/batch-') ||
123
+ /^\/runtime\/sessions\/[^/]+\/ui-results$/.test(url.pathname) ||
97
124
  /^\/runtime\/sessions\/[^/]+\/prediction$/.test(url.pathname);
98
- if (authorizeRuntime(state, request) === 'control' && workspaceId &&
99
- (!['GET', 'HEAD', 'OPTIONS'].includes(request.method ?? 'GET') || url.pathname === '/runtime/git-bundle') && !ownLease) {
125
+ if (authorizeRuntime(state, request) === 'control' &&
126
+ workspaceId &&
127
+ (!['GET', 'HEAD', 'OPTIONS'].includes(request.method ?? 'GET') ||
128
+ url.pathname === '/runtime/git-bundle') &&
129
+ !ownLease) {
100
130
  try {
101
- return await withDirectoryWrite(state, { workspaceId, directoryName: url.searchParams.get('directoryName') ?? 'basic', wait: false,
131
+ return await withDirectoryWrite(state, {
132
+ workspaceId,
133
+ directoryName: url.searchParams.get('directoryName') ?? 'basic',
134
+ wait: false,
102
135
  sessionId: url.searchParams.get('sessionId') ?? undefined,
103
- ...(url.pathname === '/runtime/git-bundle' ? { mode: 'exclusive' } : {}) }, () => dispatchRuntimeRequest(state, request, response));
136
+ ...(url.pathname === '/runtime/git-bundle' ? { mode: 'exclusive' } : {}),
137
+ }, () => dispatchRuntimeRequest(state, request, response));
104
138
  }
105
139
  catch (error) {
106
- if (!response.headersSent)
107
- writeJson(response, error instanceof Error && error.message === 'directory_busy' ? 409 : 500, { error: error instanceof Error && error.message === 'directory_busy' ? 'directory_busy' : 'runtime_request_failed' });
140
+ if (!response.headersSent) {
141
+ const busy = error instanceof Error && error.message === 'directory_busy';
142
+ writeJson(response, busy ? 409 : 500, {
143
+ error: busy
144
+ ? await directoryBusyCode(state, workspaceId, url.searchParams.get('directoryName') ?? 'basic')
145
+ : 'runtime_request_failed',
146
+ });
147
+ }
108
148
  else
109
149
  response.destroy(error instanceof Error ? error : undefined);
110
150
  return;
@@ -160,7 +200,7 @@ async function dispatchRuntimeRequest(state, request, response) {
160
200
  }
161
201
  const workspaceId = parsed.data;
162
202
  const root = await sessionWorkspaceRoot(state, workspaceId, directoryName);
163
- const rootStat = await stat(root).catch(error => {
203
+ const rootStat = await stat(root).catch((error) => {
164
204
  if (['ENOENT', 'ENOTDIR'].includes(error.code))
165
205
  throw new WorkspaceContextError('workspace_not_prepared', { cause: error });
166
206
  throw error;
@@ -202,6 +242,7 @@ async function dispatchRuntimeRequest(state, request, response) {
202
242
  levelEditorRetryVersion: 1,
203
243
  remixReferencesVersion: 1,
204
244
  batchSourceFreezeRevision: 2,
245
+ batchStarterTemplateVersion: 1,
205
246
  batchSourcePublicationVersion: 1,
206
247
  batchReferenceContextVersion: 1,
207
248
  ...(state.native?.workspaceUi ? { workspaceUiVersion: 1 } : {}),
@@ -210,10 +251,14 @@ async function dispatchRuntimeRequest(state, request, response) {
210
251
  ...(state.native?.startPrediction ? { predictionVersion: 1 } : {}),
211
252
  sourceReadToken: state.journal.sourceReadToken(),
212
253
  workspaceMode: state.config.workspaceMode,
254
+ // Runtime compares these with Business and registers again a binding this sandbox missed.
255
+ workspaceBindings: state.journal.listWorkspaces().map(workspaceBindingIdentity),
213
256
  executionGroup: {
214
257
  replaced: state.journal.executionGroup().replaced,
215
258
  stopConfirmed: state.journal.executionGroup().stopConfirmed,
216
- ...(state.journal.stoppedRuntimeEpochs().length ? { stoppedRuntimeEpochs: state.journal.stoppedRuntimeEpochs() } : {}),
259
+ ...(state.journal.stoppedRuntimeEpochs().length
260
+ ? { stoppedRuntimeEpochs: state.journal.stoppedRuntimeEpochs() }
261
+ : {}),
217
262
  },
218
263
  });
219
264
  return;
@@ -230,8 +275,12 @@ async function dispatchRuntimeRequest(state, request, response) {
230
275
  writeJson(response, 409, { error: 'multi_workspace_unavailable' });
231
276
  return;
232
277
  }
233
- const scope = workspaceSessionRestoreResponseSchema.pick({ sessionId: true, workspaceId: true, directoryName: true }).safeParse({
234
- sessionId: decodeURIComponent(restoreRoute[1]), workspaceId: url.searchParams.get('workspaceId'), directoryName,
278
+ const scope = workspaceSessionRestoreResponseSchema
279
+ .pick({ sessionId: true, workspaceId: true, directoryName: true })
280
+ .safeParse({
281
+ sessionId: decodeURIComponent(restoreRoute[1]),
282
+ workspaceId: url.searchParams.get('workspaceId'),
283
+ directoryName,
235
284
  });
236
285
  const parsed = workspaceSessionRestoreRequestSchema.safeParse(await readJson(request, 4096));
237
286
  if (!scope.success || !parsed.success) {
@@ -245,8 +294,11 @@ async function dispatchRuntimeRequest(state, request, response) {
245
294
  return state.journal.exclusive(`session:${scope.data.sessionId}`, () => state.journal.exclusive(`workspace:${revisionScope(state, scope.data.workspaceId, directoryName) ?? 'single'}`, () => restoreNativeSession(state, scope.data, parsed.data)));
246
295
  });
247
296
  writeJson(response, 200, workspaceSessionRestoreResponseSchema.parse({
248
- ...scope.data, threadId: restored.threadId, generation: parsed.data.generation,
249
- runtimeEpoch: parsed.data.runtimeEpoch, recoverability: 'native',
297
+ ...scope.data,
298
+ threadId: restored.threadId,
299
+ generation: parsed.data.generation,
300
+ runtimeEpoch: parsed.data.runtimeEpoch,
301
+ recoverability: 'native',
250
302
  }));
251
303
  }
252
304
  catch (error) {
@@ -267,8 +319,10 @@ async function dispatchRuntimeRequest(state, request, response) {
267
319
  return;
268
320
  }
269
321
  const session = state.journal.getSession(sessionId);
270
- if (!session || productionSessionAttemptId(sessionId) || session.workspaceId !== url.searchParams.get('workspaceId')
271
- || (session.directoryName ?? 'basic') !== directoryName) {
322
+ if (!session ||
323
+ productionSessionAttemptId(sessionId) ||
324
+ session.workspaceId !== url.searchParams.get('workspaceId') ||
325
+ (session.directoryName ?? 'basic') !== directoryName) {
272
326
  writeJson(response, 404, { error: 'session_not_found' });
273
327
  return;
274
328
  }
@@ -286,8 +340,10 @@ async function dispatchRuntimeRequest(state, request, response) {
286
340
  return;
287
341
  const sessionId = decodeURIComponent(predictionRoute[1]);
288
342
  const session = state.journal.getSession(sessionId);
289
- if (!session || productionSessionAttemptId(sessionId) || session.workspaceId !== url.searchParams.get('workspaceId')
290
- || (session.directoryName ?? 'basic') !== directoryName) {
343
+ if (!session ||
344
+ productionSessionAttemptId(sessionId) ||
345
+ session.workspaceId !== url.searchParams.get('workspaceId') ||
346
+ (session.directoryName ?? 'basic') !== directoryName) {
291
347
  writeJson(response, 404, { error: 'session_not_found' });
292
348
  return;
293
349
  }
@@ -305,7 +361,9 @@ async function dispatchRuntimeRequest(state, request, response) {
305
361
  if (writeDestroyed(state, response))
306
362
  return;
307
363
  const parsed = workspaceRegistrationRequestSchema.safeParse(await readJson(request));
308
- if (!parsed.success || parsed.data.generation !== state.config.generation || parsed.data.runtimeEpoch !== state.runtimeEpoch) {
364
+ if (!parsed.success ||
365
+ parsed.data.generation !== state.config.generation ||
366
+ parsed.data.runtimeEpoch !== state.runtimeEpoch) {
309
367
  writeJson(response, 409, { error: 'invalid_workspace_registration' });
310
368
  return;
311
369
  }
@@ -319,7 +377,9 @@ async function dispatchRuntimeRequest(state, request, response) {
319
377
  if (writeDestroyed(state, response))
320
378
  return;
321
379
  const body = asRecord(await readJson(request));
322
- if (body.generation !== state.config.generation || body.runtimeEpoch !== state.runtimeEpoch || !Number.isSafeInteger(body.leaseFence)) {
380
+ if (body.generation !== state.config.generation ||
381
+ body.runtimeEpoch !== state.runtimeEpoch ||
382
+ !Number.isSafeInteger(body.leaseFence)) {
323
383
  writeJson(response, 409, { error: 'invalid_workspace_registration' });
324
384
  return;
325
385
  }
@@ -331,7 +391,12 @@ async function dispatchRuntimeRequest(state, request, response) {
331
391
  return;
332
392
  }
333
393
  const directory = workspaceDirectoryRegistrationSchema.parse(body.directory);
334
- writeJson(response, 200, await withDirectoryWrite(state, { ...directory, mode: 'exclusive', wait: false }, () => withDirectoryWrite(state, { workspaceId: directory.workspaceId, directoryName: source.data, mode: 'exclusive', wait: false }, () => registerChatWorktree(state, directory, Number(body.leaseFence), source.data, body.sourceBranchName, attachments.data))));
394
+ writeJson(response, 200, await withDirectoryWrite(state, { ...directory, mode: 'exclusive', wait: false }, () => withDirectoryWrite(state, {
395
+ workspaceId: directory.workspaceId,
396
+ directoryName: source.data,
397
+ mode: 'exclusive',
398
+ wait: false,
399
+ }, () => registerChatWorktree(state, directory, Number(body.leaseFence), source.data, body.sourceBranchName, attachments.data))));
335
400
  }
336
401
  else {
337
402
  const directory = workspaceDirectoryRegistrationSchema.parse(body.directory);
@@ -339,17 +404,27 @@ async function dispatchRuntimeRequest(state, request, response) {
339
404
  }
340
405
  return;
341
406
  }
342
- if (request.method === 'POST' && url.pathname === '/runtime/workspaces/directories/worktree-source') {
407
+ if (request.method === 'POST' &&
408
+ url.pathname === '/runtime/workspaces/directories/worktree-source') {
343
409
  if (writeDestroyed(state, response))
344
410
  return;
345
411
  const body = asRecord(await readJson(request));
346
412
  const source = workspaceDirectoryNameSchema.safeParse(body.sourceDirectoryName);
347
- if (body.generation !== state.config.generation || body.runtimeEpoch !== state.runtimeEpoch || !Number.isSafeInteger(body.leaseFence)
348
- || !source.success || typeof body.workspaceId !== 'string' || typeof body.sourceBranchName !== 'string') {
413
+ if (body.generation !== state.config.generation ||
414
+ body.runtimeEpoch !== state.runtimeEpoch ||
415
+ !Number.isSafeInteger(body.leaseFence) ||
416
+ !source.success ||
417
+ typeof body.workspaceId !== 'string' ||
418
+ typeof body.sourceBranchName !== 'string') {
349
419
  writeJson(response, 409, { error: 'invalid_workspace_registration' });
350
420
  return;
351
421
  }
352
- writeJson(response, 200, await withDirectoryWrite(state, { workspaceId: body.workspaceId, directoryName: source.data, mode: 'exclusive', wait: false }, () => readWorktreeSource(state, body.workspaceId, source.data, Number(body.leaseFence), body.sourceBranchName)));
422
+ writeJson(response, 200, await withDirectoryWrite(state, {
423
+ workspaceId: body.workspaceId,
424
+ directoryName: source.data,
425
+ mode: 'exclusive',
426
+ wait: false,
427
+ }, () => readWorktreeSource(state, body.workspaceId, source.data, Number(body.leaseFence), body.sourceBranchName)));
353
428
  return;
354
429
  }
355
430
  const maintenance = /^\/runtime\/workspaces\/([a-f0-9-]{36})\/directories\/(cleanup|sync|stats)$/.exec(url.pathname);
@@ -387,11 +462,15 @@ async function dispatchRuntimeRequest(state, request, response) {
387
462
  await handleSessionHistory(state, response, historyPath.workspaceId, historyPath.sessionId);
388
463
  return;
389
464
  }
390
- if (['GET', 'HEAD', 'POST'].includes(request.method ?? '') && url.pathname === '/runtime/session-image') {
465
+ if (['GET', 'HEAD', 'POST'].includes(request.method ?? '') &&
466
+ url.pathname === '/runtime/session-image') {
391
467
  let recordedPath;
392
468
  if (request.method === 'POST') {
393
469
  const body = asRecord(await readJson(request));
394
- if (Object.keys(body).length !== 1 || typeof body.path !== 'string' || !body.path || body.path.length > 4096) {
470
+ if (Object.keys(body).length !== 1 ||
471
+ typeof body.path !== 'string' ||
472
+ !body.path ||
473
+ body.path.length > 4096) {
395
474
  writeJson(response, 404, { error: 'image_not_found' });
396
475
  return;
397
476
  }
@@ -410,15 +489,18 @@ async function dispatchRuntimeRequest(state, request, response) {
410
489
  await handleSourceEvents(state, request, response, url);
411
490
  return;
412
491
  }
413
- if (url.pathname === '/runtime/attachments/library' && ['GET', 'POST'].includes(request.method ?? '')) {
492
+ if (url.pathname === '/runtime/attachments/library' &&
493
+ ['GET', 'POST'].includes(request.method ?? '')) {
414
494
  if (writeDestroyed(state, response))
415
495
  return;
416
496
  const workspaceId = url.searchParams.get('workspaceId') ?? '';
417
497
  const root = await workspaceRoot(state, workspaceId, directoryName);
418
498
  if (request.method === 'GET') {
419
499
  const sessionId = url.searchParams.get('sessionId') ?? undefined;
420
- writeJson(response, 200, { attachments: await listWorkspaceLibraryAttachments(root, sessionId),
421
- importReceipts: await listWorkspaceLibraryImportReceipts(root, sessionId) });
500
+ writeJson(response, 200, {
501
+ attachments: await listWorkspaceLibraryAttachments(root, sessionId),
502
+ importReceipts: await listWorkspaceLibraryImportReceipts(root, sessionId),
503
+ });
422
504
  }
423
505
  else {
424
506
  if (await batchDirectoryHeld(state, workspaceId, directoryName)) {
@@ -428,15 +510,25 @@ async function dispatchRuntimeRequest(state, request, response) {
428
510
  try {
429
511
  const attachment = await importWorkspaceLibraryAttachment(root, await readLibraryAttachmentUpload(request));
430
512
  await state.journal.allocateSourceEvent({
431
- runtimeId: state.config.runtimeId, generation: state.config.generation, runtimeEpoch: state.runtimeEpoch,
432
- workspaceId, type: 'attachments.imported', occurredAt: new Date().toISOString(),
513
+ runtimeId: state.config.runtimeId,
514
+ generation: state.config.generation,
515
+ runtimeEpoch: state.runtimeEpoch,
516
+ workspaceId,
517
+ type: 'attachments.imported',
518
+ occurredAt: new Date().toISOString(),
433
519
  payload: { workspaceId, directoryName, attachmentId: attachment.id },
434
520
  });
435
521
  writeJson(response, 200, attachment);
436
522
  }
437
523
  catch (error) {
438
- const code = error instanceof Error && ['library_asset_too_large', 'library_asset_changed', 'library_attachment_limit'].includes(error.message)
439
- ? error.message : 'invalid_library_attachment';
524
+ const code = error instanceof Error &&
525
+ [
526
+ 'library_asset_too_large',
527
+ 'library_asset_changed',
528
+ 'library_attachment_limit',
529
+ ].includes(error.message)
530
+ ? error.message
531
+ : 'invalid_library_attachment';
440
532
  writeJson(response, 400, { error: code });
441
533
  }
442
534
  }
@@ -461,8 +553,12 @@ async function dispatchRuntimeRequest(state, request, response) {
461
553
  else if (uploadId && action === 'complete' && request.method === 'POST') {
462
554
  const attachment = await completeWorkspaceDirectoryAttachment(root, uploadId);
463
555
  await state.journal.allocateSourceEvent({
464
- runtimeId: state.config.runtimeId, generation: state.config.generation, runtimeEpoch: state.runtimeEpoch,
465
- workspaceId, type: 'attachments.imported', occurredAt: new Date().toISOString(),
556
+ runtimeId: state.config.runtimeId,
557
+ generation: state.config.generation,
558
+ runtimeEpoch: state.runtimeEpoch,
559
+ workspaceId,
560
+ type: 'attachments.imported',
561
+ occurredAt: new Date().toISOString(),
466
562
  payload: { workspaceId, directoryName, attachmentId: attachment.id },
467
563
  });
468
564
  writeJson(response, 200, attachment);
@@ -473,7 +569,11 @@ async function dispatchRuntimeRequest(state, request, response) {
473
569
  writeJson(response, 400, { error: 'invalid_attachment' });
474
570
  }
475
571
  catch (error) {
476
- writeJson(response, 400, { error: error instanceof Error && error.message === 'video_probe_unavailable' ? 'video_probe_unavailable' : 'invalid_attachment' });
572
+ writeJson(response, 400, {
573
+ error: error instanceof Error && error.message === 'video_probe_unavailable'
574
+ ? 'video_probe_unavailable'
575
+ : 'invalid_attachment',
576
+ });
477
577
  }
478
578
  return;
479
579
  }
@@ -487,10 +587,16 @@ async function dispatchRuntimeRequest(state, request, response) {
487
587
  }
488
588
  const root = await workspaceRoot(state, workspaceId, directoryName);
489
589
  try {
490
- writeJson(response, 200, await uploadWorkspaceVideoAttachment(root, request, { name: url.searchParams.get('name') }));
590
+ writeJson(response, 200, await uploadWorkspaceVideoAttachment(root, request, {
591
+ name: url.searchParams.get('name'),
592
+ }));
491
593
  }
492
594
  catch (error) {
493
- writeJson(response, 400, { error: error instanceof Error && error.message === 'video_probe_unavailable' ? 'video_probe_unavailable' : 'invalid_attachment' });
595
+ writeJson(response, 400, {
596
+ error: error instanceof Error && error.message === 'video_probe_unavailable'
597
+ ? 'video_probe_unavailable'
598
+ : 'invalid_attachment',
599
+ });
494
600
  }
495
601
  return;
496
602
  }
@@ -511,7 +617,8 @@ async function dispatchRuntimeRequest(state, request, response) {
511
617
  }
512
618
  return;
513
619
  }
514
- if (['GET', 'HEAD'].includes(request.method ?? '') && url.pathname === '/runtime/attachment-content') {
620
+ if (['GET', 'HEAD'].includes(request.method ?? '') &&
621
+ url.pathname === '/runtime/attachment-content') {
515
622
  if (writeDestroyed(state, response))
516
623
  return;
517
624
  const root = await workspaceRoot(state, url.searchParams.get('workspaceId') ?? '', directoryName);
@@ -558,8 +665,7 @@ async function dispatchRuntimeRequest(state, request, response) {
558
665
  return;
559
666
  }
560
667
  await workspaceRoot(state, workspaceId, directoryName);
561
- const items = await new DeliveryStore(state.config.stateDir, workspaceId)
562
- .list(directoryName, parsed.data.channel, parsed.data.limit);
668
+ const items = await new DeliveryStore(state.config.stateDir, workspaceId).list(directoryName, parsed.data.channel, parsed.data.limit);
563
669
  writeJson(response, 200, deliveryBuildListSchema.parse({ items, scannedAt: new Date().toISOString() }));
564
670
  return;
565
671
  }
@@ -594,7 +700,8 @@ async function dispatchRuntimeRequest(state, request, response) {
594
700
  writeJson(response, 200, deliveryBuildDetailSchema.parse({ record, ...(report ? { report } : {}) }));
595
701
  return;
596
702
  }
597
- if (['GET', 'HEAD'].includes(request.method ?? '') && url.pathname === '/runtime/model-preview-content') {
703
+ if (['GET', 'HEAD'].includes(request.method ?? '') &&
704
+ url.pathname === '/runtime/model-preview-content') {
598
705
  if (writeDestroyed(state, response))
599
706
  return;
600
707
  const workspaceId = url.searchParams.get('workspaceId');
@@ -605,10 +712,15 @@ async function dispatchRuntimeRequest(state, request, response) {
605
712
  const root = await workspaceRoot(state, workspaceId, directoryName);
606
713
  try {
607
714
  const output = await new ModelPreviewStore(state.config.stateDir, root).content(url.searchParams.get('previewId') ?? '');
608
- response.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'content-length': output.bytes.length,
609
- 'cache-control': 'private, no-store', 'x-content-type-options': 'nosniff', 'x-content-sha256': output.record.contentHash,
715
+ response.writeHead(200, {
716
+ 'content-type': 'text/html; charset=utf-8',
717
+ 'content-length': output.bytes.length,
718
+ 'cache-control': 'private, no-store',
719
+ 'x-content-type-options': 'nosniff',
720
+ 'x-content-sha256': output.record.contentHash,
610
721
  'content-security-policy': "sandbox allow-scripts; default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; connect-src 'none'",
611
- 'referrer-policy': 'no-referrer' });
722
+ 'referrer-policy': 'no-referrer',
723
+ });
612
724
  response.end(request.method === 'HEAD' ? undefined : output.bytes);
613
725
  }
614
726
  catch {
@@ -663,10 +775,15 @@ async function dispatchRuntimeRequest(state, request, response) {
663
775
  }
664
776
  await workspaceRoot(state, workspaceId, directoryName);
665
777
  const parsed = workspaceArtifactContentQuerySchema.safeParse({
666
- artifactId: url.searchParams.get('artifactId'), versionId: url.searchParams.get('versionId'),
778
+ artifactId: url.searchParams.get('artifactId'),
779
+ versionId: url.searchParams.get('versionId'),
667
780
  ...(url.searchParams.has('path') ? { path: url.searchParams.get('path') } : {}),
668
- ...(url.searchParams.has('offset') ? { offset: Number(url.searchParams.get('offset')) } : {}),
669
- ...(url.searchParams.has('length') ? { length: Number(url.searchParams.get('length')) } : {}),
781
+ ...(url.searchParams.has('offset')
782
+ ? { offset: Number(url.searchParams.get('offset')) }
783
+ : {}),
784
+ ...(url.searchParams.has('length')
785
+ ? { length: Number(url.searchParams.get('length')) }
786
+ : {}),
670
787
  });
671
788
  if (!parsed.success) {
672
789
  writeJson(response, 400, { error: 'invalid_artifact_query' });
@@ -692,17 +809,27 @@ async function dispatchRuntimeRequest(state, request, response) {
692
809
  return;
693
810
  const archiveWorkspaceId = url.searchParams.get('workspaceId') ?? undefined;
694
811
  const root = await sessionWorkspaceRoot(state, archiveWorkspaceId, directoryName);
695
- const expectedModelPaths = state.journal.listOperations().map(({ operation }) => operation).filter(operation => operation.kind === 'asset.library.import'
696
- && (state.config.workspaceMode !== 'multi' || operation.workspaceId === archiveWorkspaceId)
697
- && (operation.directoryName ?? 'basic') === directoryName)
698
- .flatMap(operation => operation.result && 'bundleReceipt' in operation.result ? operation.result.bundleReceipt?.files ?? [] : [])
699
- .map(file => file.path).filter(path => path.toLowerCase().endsWith('.glb'));
700
- await serveWorkspaceArchive(root, request, response, { excludedRoots: [state.config.stateDir],
812
+ const expectedModelPaths = state.journal
813
+ .listOperations()
814
+ .map(({ operation }) => operation)
815
+ .filter((operation) => operation.kind === 'asset.library.import' &&
816
+ (state.config.workspaceMode !== 'multi' ||
817
+ operation.workspaceId === archiveWorkspaceId) &&
818
+ (operation.directoryName ?? 'basic') === directoryName)
819
+ .flatMap((operation) => operation.result && 'bundleReceipt' in operation.result
820
+ ? (operation.result.bundleReceipt?.files ?? [])
821
+ : [])
822
+ .map((file) => file.path)
823
+ .filter((path) => path.toLowerCase().endsWith('.glb'));
824
+ await serveWorkspaceArchive(root, request, response, {
825
+ excludedRoots: [state.config.stateDir],
701
826
  expectedModelPaths: [...new Set(expectedModelPaths)],
702
- verifyProvenance: provenance => verifyRuntimeAssetProvenance(state, provenance) });
827
+ verifyProvenance: (provenance) => verifyRuntimeAssetProvenance(state, provenance),
828
+ });
703
829
  return;
704
830
  }
705
- if (['GET', 'HEAD'].includes(request.method ?? '') && url.pathname === '/runtime/file-content') {
831
+ if (['GET', 'HEAD'].includes(request.method ?? '') &&
832
+ url.pathname === '/runtime/file-content') {
706
833
  if (writeDestroyed(state, response))
707
834
  return;
708
835
  const root = await workspaceRoot(state, url.searchParams.get('workspaceId') ?? undefined, directoryName);
@@ -738,8 +865,10 @@ async function dispatchRuntimeRequest(state, request, response) {
738
865
  }
739
866
  const controller = new AbortController();
740
867
  const abort = () => controller.abort();
741
- const closedRequest = () => { if (request.aborted || !request.complete)
742
- abort(); };
868
+ const closedRequest = () => {
869
+ if (request.aborted || !request.complete)
870
+ abort();
871
+ };
743
872
  request.once('aborted', abort);
744
873
  request.once('close', closedRequest);
745
874
  response.once('close', abort);
@@ -752,7 +881,8 @@ async function dispatchRuntimeRequest(state, request, response) {
752
881
  catch (error) {
753
882
  if (error instanceof Error && error.name === 'AbortError')
754
883
  return;
755
- if (error instanceof Error && ['invalid_workspace_search', 'invalid_workspace_search_cursor'].includes(error.message))
884
+ if (error instanceof Error &&
885
+ ['invalid_workspace_search', 'invalid_workspace_search_cursor'].includes(error.message))
756
886
  writeJson(response, 400, { error: error.message });
757
887
  else
758
888
  throw error;
@@ -820,7 +950,9 @@ async function dispatchRuntimeRequest(state, request, response) {
820
950
  catch (error) {
821
951
  if (error instanceof WorkspaceContextError)
822
952
  throw error;
823
- writeJson(response, 409, { error: error instanceof RemixReferenceError ? error.message : 'remix_reference_unavailable' });
953
+ writeJson(response, 409, {
954
+ error: error instanceof RemixReferenceError ? error.message : 'remix_reference_unavailable',
955
+ });
824
956
  }
825
957
  return;
826
958
  }
@@ -843,13 +975,17 @@ async function dispatchRuntimeRequest(state, request, response) {
843
975
  return;
844
976
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
845
977
  try {
846
- const view = await state.journal.exclusive(`workspace:${revisionScope(state, workspaceId, directoryName) ?? 'single'}`, async () => readWorkspaceSkills(await workspaceRoot(state, workspaceId, directoryName), join(state.config.stateDir, 'skill-artifacts'), entry => loadWorkspaceSkillArtifact(state, workspaceId ?? 'workspace', entry, directoryName)));
978
+ const view = await state.journal.exclusive(`workspace:${revisionScope(state, workspaceId, directoryName) ?? 'single'}`, async () => readWorkspaceSkills(await workspaceRoot(state, workspaceId, directoryName), join(state.config.stateDir, 'skill-artifacts'), (entry) => loadWorkspaceSkillArtifact(state, workspaceId ?? 'workspace', entry, directoryName)));
847
979
  writeJson(response, 200, view);
848
980
  }
849
981
  catch (error) {
850
982
  if (error instanceof WorkspaceContextError)
851
983
  throw error;
852
- writeJson(response, 409, { error: error instanceof Error && error.message.startsWith('workspace_skills_') ? error.message : 'workspace_skills_unavailable' });
984
+ writeJson(response, 409, {
985
+ error: error instanceof Error && error.message.startsWith('workspace_skills_')
986
+ ? error.message
987
+ : 'workspace_skills_unavailable',
988
+ });
853
989
  }
854
990
  return;
855
991
  }
@@ -866,10 +1002,18 @@ async function dispatchRuntimeRequest(state, request, response) {
866
1002
  }
867
1003
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
868
1004
  const root = await workspaceRoot(state, workspaceId, directoryName), scope = revisionScope(state, workspaceId, directoryName);
869
- const first = await readWorkspaceLevelEditor(root, state.journal.fileRevisions(scope), { state, workspaceId, directoryName });
1005
+ const first = await readWorkspaceLevelEditor(root, state.journal.fileRevisions(scope), {
1006
+ state,
1007
+ workspaceId,
1008
+ directoryName,
1009
+ });
870
1010
  for (const document of Object.values(first.definition?.documents ?? {}))
871
1011
  await withObservedFile(state, workspaceId, document.path, async () => undefined, directoryName);
872
- writeJson(response, 200, await readWorkspaceLevelEditor(root, state.journal.fileRevisions(scope), { state, workspaceId, directoryName }));
1012
+ writeJson(response, 200, await readWorkspaceLevelEditor(root, state.journal.fileRevisions(scope), {
1013
+ state,
1014
+ workspaceId,
1015
+ directoryName,
1016
+ }));
873
1017
  return;
874
1018
  }
875
1019
  if (request.method === 'GET' && url.pathname === '/runtime/level-editor/tasks') {
@@ -880,7 +1024,9 @@ async function dispatchRuntimeRequest(state, request, response) {
880
1024
  return;
881
1025
  }
882
1026
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
883
- writeJson(response, 200, { tasks: await listLevelEditorTasks(state, workspaceId, directoryName, await workspaceRoot(state, workspaceId, directoryName)) });
1027
+ writeJson(response, 200, {
1028
+ tasks: await listLevelEditorTasks(state, workspaceId, directoryName, await workspaceRoot(state, workspaceId, directoryName)),
1029
+ });
884
1030
  return;
885
1031
  }
886
1032
  if (request.method === 'GET' && url.pathname === '/runtime/skill-activation-requests') {
@@ -892,7 +1038,9 @@ async function dispatchRuntimeRequest(state, request, response) {
892
1038
  }
893
1039
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
894
1040
  await workspaceRoot(state, workspaceId, directoryName);
895
- writeJson(response, 200, { requests: await listSkillActivationRequests(state, workspaceId, directoryName) });
1041
+ writeJson(response, 200, {
1042
+ requests: await listSkillActivationRequests(state, workspaceId, directoryName),
1043
+ });
896
1044
  return;
897
1045
  }
898
1046
  if (request.method === 'GET' && url.pathname === '/runtime/level-editor/requests') {
@@ -904,7 +1052,9 @@ async function dispatchRuntimeRequest(state, request, response) {
904
1052
  }
905
1053
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
906
1054
  await workspaceRoot(state, workspaceId, directoryName);
907
- writeJson(response, 200, { requests: await listLevelEditorRequests(state, workspaceId, directoryName) });
1055
+ writeJson(response, 200, {
1056
+ requests: await listLevelEditorRequests(state, workspaceId, directoryName),
1057
+ });
908
1058
  return;
909
1059
  }
910
1060
  if (request.method === 'GET' && url.pathname === '/runtime/parameters') {
@@ -922,14 +1072,18 @@ async function dispatchRuntimeRequest(state, request, response) {
922
1072
  }
923
1073
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
924
1074
  const root = await workspaceRoot(state, workspaceId, directoryName);
925
- writeJson(response, 200, { tasks: await listParameterAgentTasks(state, workspaceId, directoryName, root) });
1075
+ writeJson(response, 200, {
1076
+ tasks: await listParameterAgentTasks(state, workspaceId, directoryName, root),
1077
+ });
926
1078
  return;
927
1079
  }
928
1080
  if (request.method === 'GET' && url.pathname === '/runtime/terminals') {
929
1081
  if (writeDestroyed(state, response))
930
1082
  return;
931
1083
  writeJson(response, 200, {
932
- terminals: state.journal.listTerminals(url.searchParams.get('workspaceId') ?? undefined).filter(terminal => (terminal.directoryName ?? 'basic') === directoryName),
1084
+ terminals: state.journal
1085
+ .listTerminals(url.searchParams.get('workspaceId') ?? undefined)
1086
+ .filter((terminal) => (terminal.directoryName ?? 'basic') === directoryName),
933
1087
  });
934
1088
  return;
935
1089
  }
@@ -957,7 +1111,9 @@ async function dispatchRuntimeRequest(state, request, response) {
957
1111
  directoryName,
958
1112
  sessionId: url.searchParams.get('sessionId') ?? undefined,
959
1113
  }));
960
- const observed = operation ? await observeLevelEditorTransaction(state, operation) : undefined;
1114
+ const observed = operation
1115
+ ? await observeLevelEditorTransaction(state, operation)
1116
+ : undefined;
961
1117
  writeJson(response, observed ? 200 : 404, observed ?? { error: 'operation_not_found' });
962
1118
  return;
963
1119
  }
@@ -971,8 +1127,9 @@ async function dispatchRuntimeRequest(state, request, response) {
971
1127
  return;
972
1128
  }
973
1129
  const operation = state.journal.getOperationById(operationLog[1]);
974
- if (!operation || operation.workspaceId !== workspaceId
975
- || (operation.directoryName ?? 'basic') !== directoryName) {
1130
+ if (!operation ||
1131
+ operation.workspaceId !== workspaceId ||
1132
+ (operation.directoryName ?? 'basic') !== directoryName) {
976
1133
  writeJson(response, 404, { error: 'operation_not_found' });
977
1134
  return;
978
1135
  }
@@ -996,13 +1153,16 @@ async function dispatchRuntimeRequest(state, request, response) {
996
1153
  return;
997
1154
  }
998
1155
  if (request.method === 'POST' && url.pathname === '/runtime/scheduled-task-proposals/source') {
999
- const body = await readJson(request);
1000
- if (!body || Object.keys(body).some(key => !['bridge', 'threadId', 'turnId', 'toolCallId'].includes(key))
1001
- || !['bridge', 'threadId', 'turnId', 'toolCallId'].every(key => typeof body[key] === 'string' && Boolean(body[key]))) {
1156
+ const body = (await readJson(request));
1157
+ if (!body ||
1158
+ Object.keys(body).some((key) => !['bridge', 'threadId', 'turnId', 'toolCallId'].includes(key)) ||
1159
+ !['bridge', 'threadId', 'turnId', 'toolCallId'].every((key) => typeof body[key] === 'string' && Boolean(body[key]))) {
1002
1160
  writeJson(response, 400, { valid: false });
1003
1161
  return;
1004
1162
  }
1005
- writeJson(response, 200, { valid: state.native?.scheduledTaskProposalSource?.(body) === true });
1163
+ writeJson(response, 200, {
1164
+ valid: state.native?.scheduledTaskProposalSource?.(body) === true,
1165
+ });
1006
1166
  return;
1007
1167
  }
1008
1168
  if (request.method === 'POST' && url.pathname === '/runtime/operations') {
@@ -1018,13 +1178,17 @@ async function dispatchRuntimeRequest(state, request, response) {
1018
1178
  writeJson(response, 409, { error: 'directory_busy' });
1019
1179
  return;
1020
1180
  }
1021
- if (request.method === 'GET' && ['/runtime/connectivity', '/runtime/files'].includes(url.pathname)) {
1181
+ if (request.method === 'GET' &&
1182
+ ['/runtime/connectivity', '/runtime/files'].includes(url.pathname)) {
1022
1183
  const cause = error instanceof Error && error.cause ? error.cause : error;
1023
1184
  if (['EACCES', 'EPERM'].includes(String(cause?.code))) {
1024
1185
  writeJson(response, 409, { error: 'workspace_permission_denied' });
1025
1186
  return;
1026
1187
  }
1027
- if (url.pathname === '/runtime/connectivity' && error instanceof WorkspaceContextError && error.cause && !['ENOENT', 'ENOTDIR'].includes(String(cause?.code))) {
1188
+ if (url.pathname === '/runtime/connectivity' &&
1189
+ error instanceof WorkspaceContextError &&
1190
+ error.cause &&
1191
+ !['ENOENT', 'ENOTDIR'].includes(String(cause?.code))) {
1028
1192
  writeJson(response, 500, { error: 'workspace_access_failed' });
1029
1193
  return;
1030
1194
  }
@@ -1058,7 +1222,7 @@ function handshake(state) {
1058
1222
  if (item.name === 'conversation') {
1059
1223
  if (conversationReady)
1060
1224
  return capability('conversation', true);
1061
- return capability('conversation', false, destroyed ? 'runtime_destroyed' : state.nativeError ?? 'adapter_not_started');
1225
+ return capability('conversation', false, destroyed ? 'runtime_destroyed' : (state.nativeError ?? 'adapter_not_started'));
1062
1226
  }
1063
1227
  if (item.name === 'files') {
1064
1228
  return filesReady
@@ -1083,7 +1247,9 @@ function handshake(state) {
1083
1247
  if (destroyed)
1084
1248
  return capability('desktop', false, 'runtime_destroyed');
1085
1249
  const status = desktopHost(state).snapshot().status;
1086
- return status === 'ready' ? capability('desktop', true) : capability('desktop', false, status);
1250
+ return status === 'ready'
1251
+ ? capability('desktop', true)
1252
+ : capability('desktop', false, status);
1087
1253
  }
1088
1254
  if (item.name === 'parameters') {
1089
1255
  return destroyed
@@ -1142,8 +1308,15 @@ async function acceptOperation(state, request, response) {
1142
1308
  if (parsed.data.kind === 'workspace.references.import') {
1143
1309
  const source = workspaceRemixReferenceImportRequestSchema.safeParse(parsed.data.payload);
1144
1310
  const snapshot = parsed.data.remixReferenceSnapshot;
1145
- if (!source.success || (snapshot && (snapshot.source.projectUuid !== source.data.source.projectUuid ||
1146
- snapshot.source.branchName !== source.data.source.branchName || snapshot.source.environment !== state.config.environment))) {
1311
+ if (!source.success ||
1312
+ (snapshot &&
1313
+ (snapshot.source.environment !== state.config.environment ||
1314
+ ('builtinTemplate' in source.data.source
1315
+ ? !('builtinTemplate' in snapshot.source) ||
1316
+ snapshot.source.builtinTemplate !== source.data.source.builtinTemplate
1317
+ : !('projectUuid' in snapshot.source) ||
1318
+ snapshot.source.projectUuid !== source.data.source.projectUuid ||
1319
+ snapshot.source.branchName !== source.data.source.branchName)))) {
1147
1320
  writeJson(response, 400, { error: 'invalid_remix_reference' });
1148
1321
  return;
1149
1322
  }
@@ -1152,13 +1325,20 @@ async function acceptOperation(state, request, response) {
1152
1325
  writeJson(response, 400, { error: 'invalid_operation', reason: 'sessionId_required' });
1153
1326
  return;
1154
1327
  }
1155
- if (parsed.data.directoryName === 'root' && !parsed.data.kind.startsWith('session.') && !['runtime.ensure', 'terminal.create'].includes(parsed.data.kind)) {
1328
+ if (parsed.data.directoryName === 'root' &&
1329
+ !parsed.data.kind.startsWith('session.') &&
1330
+ !['runtime.ensure', 'terminal.create'].includes(parsed.data.kind)) {
1156
1331
  writeJson(response, 400, { error: 'invalid_workspace_directory' });
1157
1332
  return;
1158
1333
  }
1159
- if (state.config.workspaceMode === 'multi' && parsed.data.kind === 'runtime.ensure' && parsed.data.workspaceId && parsed.data.directoryName !== 'root') {
1334
+ if (state.config.workspaceMode === 'multi' &&
1335
+ parsed.data.kind === 'runtime.ensure' &&
1336
+ parsed.data.workspaceId &&
1337
+ parsed.data.directoryName !== 'root') {
1160
1338
  const workspaceId = parsed.data.workspaceId, directoryName = parsed.data.directoryName ?? 'basic';
1161
- const restoring = state.journal.getWorkspace(workspaceId)?.directories?.some(directory => directory.directoryName === directoryName && directory.status === 'reclaimed') ?? false;
1339
+ const restoring = state.journal
1340
+ .getWorkspace(workspaceId)
1341
+ ?.directories?.some((directory) => directory.directoryName === directoryName && directory.status === 'reclaimed') ?? false;
1162
1342
  // Opening a resident checkout only refreshes instructions and can share its background install.
1163
1343
  // Rebuilding a reclaimed checkout must still exclude every existing writer.
1164
1344
  await withDirectoryWrite(state, { workspaceId, directoryName, mode: restoring ? 'exclusive' : 'shared', wait: false }, async () => {
@@ -1169,7 +1349,8 @@ async function acceptOperation(state, request, response) {
1169
1349
  await sessionWorkspaceRoot(state, parsed.data.workspaceId, parsed.data.directoryName);
1170
1350
  // Shared classification: a new conversation writes no files, so a batch hold must not refuse it.
1171
1351
  const occupiesDirectory = takesWorkspaceDirectory(parsed.data.kind);
1172
- if (occupiesDirectory && await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName)) {
1352
+ if (occupiesDirectory &&
1353
+ (await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName))) {
1173
1354
  writeJson(response, 409, { error: 'batch_workspace_readonly' });
1174
1355
  return;
1175
1356
  }
@@ -1178,15 +1359,23 @@ async function acceptOperation(state, request, response) {
1178
1359
  : parsed.data.kind === 'asset.library.import'
1179
1360
  ? { selection: parsed.data.payload, snapshot: parsed.data.libraryAssetImports }
1180
1361
  : parsed.data.kind === 'session.turn' && parsed.data.scheduledTaskProposalBridge
1181
- ? { ...asRecord(parsed.data.payload), scheduledTaskProposalBridge: parsed.data.scheduledTaskProposalBridge }
1362
+ ? {
1363
+ ...asRecord(parsed.data.payload),
1364
+ scheduledTaskProposalBridge: parsed.data.scheduledTaskProposalBridge,
1365
+ }
1182
1366
  : parsed.data.payload;
1183
1367
  const accept = async () => {
1184
1368
  const result = await acceptOperationCore(state, parsed.data, {
1185
1369
  leaseFence: incomingFence,
1186
1370
  executionPayload,
1187
1371
  validate: async (scopeKey) => {
1188
- if (parsed.data.kind.startsWith('session.') && parsed.data.sessionId && productionSessionAttemptId(parsed.data.sessionId)) {
1189
- return { status: 409, body: runtimeError('capability_unavailable', 'production_session_readonly') };
1372
+ if (parsed.data.kind.startsWith('session.') &&
1373
+ parsed.data.sessionId &&
1374
+ productionSessionAttemptId(parsed.data.sessionId)) {
1375
+ return {
1376
+ status: 409,
1377
+ body: runtimeError('capability_unavailable', 'production_session_readonly'),
1378
+ };
1190
1379
  }
1191
1380
  if (parsed.data.kind === 'session.create') {
1192
1381
  const creation = state.journal.findSessionCreation(parsed.data.sessionId);
@@ -1198,7 +1387,10 @@ async function acceptOperation(state, request, response) {
1198
1387
  const payload = asRecord(parsed.data.payload);
1199
1388
  const terminalId = typeof payload.terminalId === 'string' ? payload.terminalId.trim() : '';
1200
1389
  if (!terminalId)
1201
- return { status: 400, body: { error: 'invalid_operation', reason: 'terminalId_required' } };
1390
+ return {
1391
+ status: 400,
1392
+ body: { error: 'invalid_operation', reason: 'terminalId_required' },
1393
+ };
1202
1394
  const creation = state.journal.findTerminalCreation(terminalId);
1203
1395
  const terminal = state.journal.getTerminal(terminalId);
1204
1396
  if (creation && operationScopeKey(creation) !== scopeKey) {
@@ -1207,16 +1399,20 @@ async function acceptOperation(state, request, response) {
1207
1399
  if (terminal && !creation)
1208
1400
  return { status: 409, body: { error: 'terminal_already_bound' } };
1209
1401
  }
1210
- else if (parsed.data.kind.startsWith('session.') && state.config.workspaceMode === 'multi') {
1402
+ else if (parsed.data.kind.startsWith('session.') &&
1403
+ state.config.workspaceMode === 'multi') {
1211
1404
  const session = state.journal.getSession(parsed.data.sessionId);
1212
1405
  // A replaced sandbox has no binding yet; the recovering turn creates it.
1213
1406
  const rebinding = !session && parsed.data.kind === 'session.turn' && parsed.data.sessionRecovery;
1214
- if (!rebinding && (!session || session.workspaceId !== parsed.data.workspaceId
1215
- || (session.directoryName ?? 'basic') !== (parsed.data.directoryName ?? 'basic'))) {
1407
+ if (!rebinding &&
1408
+ (!session ||
1409
+ session.workspaceId !== parsed.data.workspaceId ||
1410
+ (session.directoryName ?? 'basic') !== (parsed.data.directoryName ?? 'basic'))) {
1216
1411
  return { status: 409, body: { error: 'session_workspace_mismatch' } };
1217
1412
  }
1218
1413
  }
1219
- if (parsed.data.kind === 'workspace.references.import' && !parsed.data.remixReferenceSnapshot) {
1414
+ if (parsed.data.kind === 'workspace.references.import' &&
1415
+ !parsed.data.remixReferenceSnapshot) {
1220
1416
  return { status: 400, body: { error: 'remix_reference_snapshot_required' } };
1221
1417
  }
1222
1418
  if (parsed.data.kind === 'asset.library.import' && !parsed.data.libraryAssetImports) {
@@ -1243,7 +1439,8 @@ async function acceptOperation(state, request, response) {
1243
1439
  writeJson(response, executed.status === 'failed' ? 409 : 202, executed);
1244
1440
  };
1245
1441
  const acceptBound = async () => {
1246
- if (occupiesDirectory && await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName)) {
1442
+ if (occupiesDirectory &&
1443
+ (await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName))) {
1247
1444
  writeJson(response, 409, { error: 'batch_workspace_readonly' });
1248
1445
  return;
1249
1446
  }
@@ -1319,7 +1516,7 @@ async function handleTerminalIdentity(state, request, response, remainder) {
1319
1516
  if (writeDestroyed(state, response))
1320
1517
  return;
1321
1518
  const [terminalId, action] = remainder.split('/');
1322
- if (!terminalId || action && action !== 'connect') {
1519
+ if (!terminalId || (action && action !== 'connect')) {
1323
1520
  writeJson(response, 404, { error: 'not_found' });
1324
1521
  return;
1325
1522
  }
@@ -1424,7 +1621,12 @@ async function readObservedParameters(state, workspaceId, directoryName = 'basic
1424
1621
  const scope = revisionScope(state, workspaceId, directoryName);
1425
1622
  return state.journal.exclusive(`workspace:${scope ?? 'single'}`, async () => {
1426
1623
  const first = await readWorkspaceParameters(root, state.journal.fileRevisions(scope));
1427
- for (const path of [first.schemaPath, first.dataPath, first.runtimeI18n?.globalPath, first.runtimeI18n?.themePath]) {
1624
+ for (const path of [
1625
+ first.schemaPath,
1626
+ first.dataPath,
1627
+ first.runtimeI18n?.globalPath,
1628
+ first.runtimeI18n?.themePath,
1629
+ ]) {
1428
1630
  if (path)
1429
1631
  await withObservedFile(state, workspaceId, path, async () => undefined, directoryName);
1430
1632
  }
@@ -1502,7 +1704,10 @@ async function handleSourceEvents(state, request, response, url) {
1502
1704
  const runtimeEpoch = epochQuery === null ? state.runtimeEpoch : Number(epochQuery);
1503
1705
  const runtimeId = url.searchParams.get('runtimeId') ?? state.config.runtimeId;
1504
1706
  const generation = Number(url.searchParams.get('generation') ?? state.config.generation);
1505
- if (runtimeId !== state.config.runtimeId || generation !== state.config.generation || !Number.isInteger(runtimeEpoch) || runtimeEpoch < 0) {
1707
+ if (runtimeId !== state.config.runtimeId ||
1708
+ generation !== state.config.generation ||
1709
+ !Number.isInteger(runtimeEpoch) ||
1710
+ runtimeEpoch < 0) {
1506
1711
  writeJson(response, 403, { error: 'source_identity_mismatch' });
1507
1712
  return;
1508
1713
  }
@@ -1512,15 +1717,26 @@ async function handleSourceEvents(state, request, response, url) {
1512
1717
  runtimeEpoch,
1513
1718
  after: safeAfter,
1514
1719
  };
1515
- if (!state.journal.epochsSnapshot().some(epoch => epoch.runtime_id === runtimeId && epoch.generation === generation && epoch.runtime_epoch === runtimeEpoch)) {
1516
- writeJson(response, 409, { code: 'source_gap', retainedFrom: 1, highWater: 0, coveredThrough: safeAfter });
1720
+ if (!state.journal
1721
+ .epochsSnapshot()
1722
+ .some((epoch) => epoch.runtime_id === runtimeId &&
1723
+ epoch.generation === generation &&
1724
+ epoch.runtime_epoch === runtimeEpoch)) {
1725
+ writeJson(response, 409, {
1726
+ code: 'source_gap',
1727
+ retainedFrom: 1,
1728
+ highWater: 0,
1729
+ coveredThrough: safeAfter,
1730
+ });
1517
1731
  return;
1518
1732
  }
1519
1733
  if (String(request.headers.accept ?? '').includes('text/event-stream')) {
1520
1734
  writeSourceStream(state, request, response, query);
1521
1735
  return;
1522
1736
  }
1523
- if (epochQuery === null && !url.searchParams.has('runtimeId') && !url.searchParams.has('generation')) {
1737
+ if (epochQuery === null &&
1738
+ !url.searchParams.has('runtimeId') &&
1739
+ !url.searchParams.has('generation')) {
1524
1740
  const events = state.journal.listEvents(safeAfter);
1525
1741
  const page = state.journal.readSourcePage({ ...query, limit: 1 });
1526
1742
  writeJson(response, 200, {