@playcraft/cli 0.1.71 → 0.1.72

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,22 +20,22 @@ 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';
@@ -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 } : {}),
@@ -213,7 +254,9 @@ async function dispatchRuntimeRequest(state, request, response) {
213
254
  executionGroup: {
214
255
  replaced: state.journal.executionGroup().replaced,
215
256
  stopConfirmed: state.journal.executionGroup().stopConfirmed,
216
- ...(state.journal.stoppedRuntimeEpochs().length ? { stoppedRuntimeEpochs: state.journal.stoppedRuntimeEpochs() } : {}),
257
+ ...(state.journal.stoppedRuntimeEpochs().length
258
+ ? { stoppedRuntimeEpochs: state.journal.stoppedRuntimeEpochs() }
259
+ : {}),
217
260
  },
218
261
  });
219
262
  return;
@@ -230,8 +273,12 @@ async function dispatchRuntimeRequest(state, request, response) {
230
273
  writeJson(response, 409, { error: 'multi_workspace_unavailable' });
231
274
  return;
232
275
  }
233
- const scope = workspaceSessionRestoreResponseSchema.pick({ sessionId: true, workspaceId: true, directoryName: true }).safeParse({
234
- sessionId: decodeURIComponent(restoreRoute[1]), workspaceId: url.searchParams.get('workspaceId'), directoryName,
276
+ const scope = workspaceSessionRestoreResponseSchema
277
+ .pick({ sessionId: true, workspaceId: true, directoryName: true })
278
+ .safeParse({
279
+ sessionId: decodeURIComponent(restoreRoute[1]),
280
+ workspaceId: url.searchParams.get('workspaceId'),
281
+ directoryName,
235
282
  });
236
283
  const parsed = workspaceSessionRestoreRequestSchema.safeParse(await readJson(request, 4096));
237
284
  if (!scope.success || !parsed.success) {
@@ -245,8 +292,11 @@ async function dispatchRuntimeRequest(state, request, response) {
245
292
  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
293
  });
247
294
  writeJson(response, 200, workspaceSessionRestoreResponseSchema.parse({
248
- ...scope.data, threadId: restored.threadId, generation: parsed.data.generation,
249
- runtimeEpoch: parsed.data.runtimeEpoch, recoverability: 'native',
295
+ ...scope.data,
296
+ threadId: restored.threadId,
297
+ generation: parsed.data.generation,
298
+ runtimeEpoch: parsed.data.runtimeEpoch,
299
+ recoverability: 'native',
250
300
  }));
251
301
  }
252
302
  catch (error) {
@@ -267,8 +317,10 @@ async function dispatchRuntimeRequest(state, request, response) {
267
317
  return;
268
318
  }
269
319
  const session = state.journal.getSession(sessionId);
270
- if (!session || productionSessionAttemptId(sessionId) || session.workspaceId !== url.searchParams.get('workspaceId')
271
- || (session.directoryName ?? 'basic') !== directoryName) {
320
+ if (!session ||
321
+ productionSessionAttemptId(sessionId) ||
322
+ session.workspaceId !== url.searchParams.get('workspaceId') ||
323
+ (session.directoryName ?? 'basic') !== directoryName) {
272
324
  writeJson(response, 404, { error: 'session_not_found' });
273
325
  return;
274
326
  }
@@ -286,8 +338,10 @@ async function dispatchRuntimeRequest(state, request, response) {
286
338
  return;
287
339
  const sessionId = decodeURIComponent(predictionRoute[1]);
288
340
  const session = state.journal.getSession(sessionId);
289
- if (!session || productionSessionAttemptId(sessionId) || session.workspaceId !== url.searchParams.get('workspaceId')
290
- || (session.directoryName ?? 'basic') !== directoryName) {
341
+ if (!session ||
342
+ productionSessionAttemptId(sessionId) ||
343
+ session.workspaceId !== url.searchParams.get('workspaceId') ||
344
+ (session.directoryName ?? 'basic') !== directoryName) {
291
345
  writeJson(response, 404, { error: 'session_not_found' });
292
346
  return;
293
347
  }
@@ -305,7 +359,9 @@ async function dispatchRuntimeRequest(state, request, response) {
305
359
  if (writeDestroyed(state, response))
306
360
  return;
307
361
  const parsed = workspaceRegistrationRequestSchema.safeParse(await readJson(request));
308
- if (!parsed.success || parsed.data.generation !== state.config.generation || parsed.data.runtimeEpoch !== state.runtimeEpoch) {
362
+ if (!parsed.success ||
363
+ parsed.data.generation !== state.config.generation ||
364
+ parsed.data.runtimeEpoch !== state.runtimeEpoch) {
309
365
  writeJson(response, 409, { error: 'invalid_workspace_registration' });
310
366
  return;
311
367
  }
@@ -319,7 +375,9 @@ async function dispatchRuntimeRequest(state, request, response) {
319
375
  if (writeDestroyed(state, response))
320
376
  return;
321
377
  const body = asRecord(await readJson(request));
322
- if (body.generation !== state.config.generation || body.runtimeEpoch !== state.runtimeEpoch || !Number.isSafeInteger(body.leaseFence)) {
378
+ if (body.generation !== state.config.generation ||
379
+ body.runtimeEpoch !== state.runtimeEpoch ||
380
+ !Number.isSafeInteger(body.leaseFence)) {
323
381
  writeJson(response, 409, { error: 'invalid_workspace_registration' });
324
382
  return;
325
383
  }
@@ -331,7 +389,12 @@ async function dispatchRuntimeRequest(state, request, response) {
331
389
  return;
332
390
  }
333
391
  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))));
392
+ writeJson(response, 200, await withDirectoryWrite(state, { ...directory, mode: 'exclusive', wait: false }, () => withDirectoryWrite(state, {
393
+ workspaceId: directory.workspaceId,
394
+ directoryName: source.data,
395
+ mode: 'exclusive',
396
+ wait: false,
397
+ }, () => registerChatWorktree(state, directory, Number(body.leaseFence), source.data, body.sourceBranchName, attachments.data))));
335
398
  }
336
399
  else {
337
400
  const directory = workspaceDirectoryRegistrationSchema.parse(body.directory);
@@ -339,17 +402,27 @@ async function dispatchRuntimeRequest(state, request, response) {
339
402
  }
340
403
  return;
341
404
  }
342
- if (request.method === 'POST' && url.pathname === '/runtime/workspaces/directories/worktree-source') {
405
+ if (request.method === 'POST' &&
406
+ url.pathname === '/runtime/workspaces/directories/worktree-source') {
343
407
  if (writeDestroyed(state, response))
344
408
  return;
345
409
  const body = asRecord(await readJson(request));
346
410
  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') {
411
+ if (body.generation !== state.config.generation ||
412
+ body.runtimeEpoch !== state.runtimeEpoch ||
413
+ !Number.isSafeInteger(body.leaseFence) ||
414
+ !source.success ||
415
+ typeof body.workspaceId !== 'string' ||
416
+ typeof body.sourceBranchName !== 'string') {
349
417
  writeJson(response, 409, { error: 'invalid_workspace_registration' });
350
418
  return;
351
419
  }
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)));
420
+ writeJson(response, 200, await withDirectoryWrite(state, {
421
+ workspaceId: body.workspaceId,
422
+ directoryName: source.data,
423
+ mode: 'exclusive',
424
+ wait: false,
425
+ }, () => readWorktreeSource(state, body.workspaceId, source.data, Number(body.leaseFence), body.sourceBranchName)));
353
426
  return;
354
427
  }
355
428
  const maintenance = /^\/runtime\/workspaces\/([a-f0-9-]{36})\/directories\/(cleanup|sync|stats)$/.exec(url.pathname);
@@ -387,11 +460,15 @@ async function dispatchRuntimeRequest(state, request, response) {
387
460
  await handleSessionHistory(state, response, historyPath.workspaceId, historyPath.sessionId);
388
461
  return;
389
462
  }
390
- if (['GET', 'HEAD', 'POST'].includes(request.method ?? '') && url.pathname === '/runtime/session-image') {
463
+ if (['GET', 'HEAD', 'POST'].includes(request.method ?? '') &&
464
+ url.pathname === '/runtime/session-image') {
391
465
  let recordedPath;
392
466
  if (request.method === 'POST') {
393
467
  const body = asRecord(await readJson(request));
394
- if (Object.keys(body).length !== 1 || typeof body.path !== 'string' || !body.path || body.path.length > 4096) {
468
+ if (Object.keys(body).length !== 1 ||
469
+ typeof body.path !== 'string' ||
470
+ !body.path ||
471
+ body.path.length > 4096) {
395
472
  writeJson(response, 404, { error: 'image_not_found' });
396
473
  return;
397
474
  }
@@ -410,15 +487,18 @@ async function dispatchRuntimeRequest(state, request, response) {
410
487
  await handleSourceEvents(state, request, response, url);
411
488
  return;
412
489
  }
413
- if (url.pathname === '/runtime/attachments/library' && ['GET', 'POST'].includes(request.method ?? '')) {
490
+ if (url.pathname === '/runtime/attachments/library' &&
491
+ ['GET', 'POST'].includes(request.method ?? '')) {
414
492
  if (writeDestroyed(state, response))
415
493
  return;
416
494
  const workspaceId = url.searchParams.get('workspaceId') ?? '';
417
495
  const root = await workspaceRoot(state, workspaceId, directoryName);
418
496
  if (request.method === 'GET') {
419
497
  const sessionId = url.searchParams.get('sessionId') ?? undefined;
420
- writeJson(response, 200, { attachments: await listWorkspaceLibraryAttachments(root, sessionId),
421
- importReceipts: await listWorkspaceLibraryImportReceipts(root, sessionId) });
498
+ writeJson(response, 200, {
499
+ attachments: await listWorkspaceLibraryAttachments(root, sessionId),
500
+ importReceipts: await listWorkspaceLibraryImportReceipts(root, sessionId),
501
+ });
422
502
  }
423
503
  else {
424
504
  if (await batchDirectoryHeld(state, workspaceId, directoryName)) {
@@ -428,15 +508,25 @@ async function dispatchRuntimeRequest(state, request, response) {
428
508
  try {
429
509
  const attachment = await importWorkspaceLibraryAttachment(root, await readLibraryAttachmentUpload(request));
430
510
  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(),
511
+ runtimeId: state.config.runtimeId,
512
+ generation: state.config.generation,
513
+ runtimeEpoch: state.runtimeEpoch,
514
+ workspaceId,
515
+ type: 'attachments.imported',
516
+ occurredAt: new Date().toISOString(),
433
517
  payload: { workspaceId, directoryName, attachmentId: attachment.id },
434
518
  });
435
519
  writeJson(response, 200, attachment);
436
520
  }
437
521
  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';
522
+ const code = error instanceof Error &&
523
+ [
524
+ 'library_asset_too_large',
525
+ 'library_asset_changed',
526
+ 'library_attachment_limit',
527
+ ].includes(error.message)
528
+ ? error.message
529
+ : 'invalid_library_attachment';
440
530
  writeJson(response, 400, { error: code });
441
531
  }
442
532
  }
@@ -461,8 +551,12 @@ async function dispatchRuntimeRequest(state, request, response) {
461
551
  else if (uploadId && action === 'complete' && request.method === 'POST') {
462
552
  const attachment = await completeWorkspaceDirectoryAttachment(root, uploadId);
463
553
  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(),
554
+ runtimeId: state.config.runtimeId,
555
+ generation: state.config.generation,
556
+ runtimeEpoch: state.runtimeEpoch,
557
+ workspaceId,
558
+ type: 'attachments.imported',
559
+ occurredAt: new Date().toISOString(),
466
560
  payload: { workspaceId, directoryName, attachmentId: attachment.id },
467
561
  });
468
562
  writeJson(response, 200, attachment);
@@ -473,7 +567,11 @@ async function dispatchRuntimeRequest(state, request, response) {
473
567
  writeJson(response, 400, { error: 'invalid_attachment' });
474
568
  }
475
569
  catch (error) {
476
- writeJson(response, 400, { error: error instanceof Error && error.message === 'video_probe_unavailable' ? 'video_probe_unavailable' : 'invalid_attachment' });
570
+ writeJson(response, 400, {
571
+ error: error instanceof Error && error.message === 'video_probe_unavailable'
572
+ ? 'video_probe_unavailable'
573
+ : 'invalid_attachment',
574
+ });
477
575
  }
478
576
  return;
479
577
  }
@@ -487,10 +585,16 @@ async function dispatchRuntimeRequest(state, request, response) {
487
585
  }
488
586
  const root = await workspaceRoot(state, workspaceId, directoryName);
489
587
  try {
490
- writeJson(response, 200, await uploadWorkspaceVideoAttachment(root, request, { name: url.searchParams.get('name') }));
588
+ writeJson(response, 200, await uploadWorkspaceVideoAttachment(root, request, {
589
+ name: url.searchParams.get('name'),
590
+ }));
491
591
  }
492
592
  catch (error) {
493
- writeJson(response, 400, { error: error instanceof Error && error.message === 'video_probe_unavailable' ? 'video_probe_unavailable' : 'invalid_attachment' });
593
+ writeJson(response, 400, {
594
+ error: error instanceof Error && error.message === 'video_probe_unavailable'
595
+ ? 'video_probe_unavailable'
596
+ : 'invalid_attachment',
597
+ });
494
598
  }
495
599
  return;
496
600
  }
@@ -511,7 +615,8 @@ async function dispatchRuntimeRequest(state, request, response) {
511
615
  }
512
616
  return;
513
617
  }
514
- if (['GET', 'HEAD'].includes(request.method ?? '') && url.pathname === '/runtime/attachment-content') {
618
+ if (['GET', 'HEAD'].includes(request.method ?? '') &&
619
+ url.pathname === '/runtime/attachment-content') {
515
620
  if (writeDestroyed(state, response))
516
621
  return;
517
622
  const root = await workspaceRoot(state, url.searchParams.get('workspaceId') ?? '', directoryName);
@@ -558,8 +663,7 @@ async function dispatchRuntimeRequest(state, request, response) {
558
663
  return;
559
664
  }
560
665
  await workspaceRoot(state, workspaceId, directoryName);
561
- const items = await new DeliveryStore(state.config.stateDir, workspaceId)
562
- .list(directoryName, parsed.data.channel, parsed.data.limit);
666
+ const items = await new DeliveryStore(state.config.stateDir, workspaceId).list(directoryName, parsed.data.channel, parsed.data.limit);
563
667
  writeJson(response, 200, deliveryBuildListSchema.parse({ items, scannedAt: new Date().toISOString() }));
564
668
  return;
565
669
  }
@@ -594,7 +698,8 @@ async function dispatchRuntimeRequest(state, request, response) {
594
698
  writeJson(response, 200, deliveryBuildDetailSchema.parse({ record, ...(report ? { report } : {}) }));
595
699
  return;
596
700
  }
597
- if (['GET', 'HEAD'].includes(request.method ?? '') && url.pathname === '/runtime/model-preview-content') {
701
+ if (['GET', 'HEAD'].includes(request.method ?? '') &&
702
+ url.pathname === '/runtime/model-preview-content') {
598
703
  if (writeDestroyed(state, response))
599
704
  return;
600
705
  const workspaceId = url.searchParams.get('workspaceId');
@@ -605,10 +710,15 @@ async function dispatchRuntimeRequest(state, request, response) {
605
710
  const root = await workspaceRoot(state, workspaceId, directoryName);
606
711
  try {
607
712
  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,
713
+ response.writeHead(200, {
714
+ 'content-type': 'text/html; charset=utf-8',
715
+ 'content-length': output.bytes.length,
716
+ 'cache-control': 'private, no-store',
717
+ 'x-content-type-options': 'nosniff',
718
+ 'x-content-sha256': output.record.contentHash,
610
719
  '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' });
720
+ 'referrer-policy': 'no-referrer',
721
+ });
612
722
  response.end(request.method === 'HEAD' ? undefined : output.bytes);
613
723
  }
614
724
  catch {
@@ -663,10 +773,15 @@ async function dispatchRuntimeRequest(state, request, response) {
663
773
  }
664
774
  await workspaceRoot(state, workspaceId, directoryName);
665
775
  const parsed = workspaceArtifactContentQuerySchema.safeParse({
666
- artifactId: url.searchParams.get('artifactId'), versionId: url.searchParams.get('versionId'),
776
+ artifactId: url.searchParams.get('artifactId'),
777
+ versionId: url.searchParams.get('versionId'),
667
778
  ...(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')) } : {}),
779
+ ...(url.searchParams.has('offset')
780
+ ? { offset: Number(url.searchParams.get('offset')) }
781
+ : {}),
782
+ ...(url.searchParams.has('length')
783
+ ? { length: Number(url.searchParams.get('length')) }
784
+ : {}),
670
785
  });
671
786
  if (!parsed.success) {
672
787
  writeJson(response, 400, { error: 'invalid_artifact_query' });
@@ -692,17 +807,27 @@ async function dispatchRuntimeRequest(state, request, response) {
692
807
  return;
693
808
  const archiveWorkspaceId = url.searchParams.get('workspaceId') ?? undefined;
694
809
  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],
810
+ const expectedModelPaths = state.journal
811
+ .listOperations()
812
+ .map(({ operation }) => operation)
813
+ .filter((operation) => operation.kind === 'asset.library.import' &&
814
+ (state.config.workspaceMode !== 'multi' ||
815
+ operation.workspaceId === archiveWorkspaceId) &&
816
+ (operation.directoryName ?? 'basic') === directoryName)
817
+ .flatMap((operation) => operation.result && 'bundleReceipt' in operation.result
818
+ ? (operation.result.bundleReceipt?.files ?? [])
819
+ : [])
820
+ .map((file) => file.path)
821
+ .filter((path) => path.toLowerCase().endsWith('.glb'));
822
+ await serveWorkspaceArchive(root, request, response, {
823
+ excludedRoots: [state.config.stateDir],
701
824
  expectedModelPaths: [...new Set(expectedModelPaths)],
702
- verifyProvenance: provenance => verifyRuntimeAssetProvenance(state, provenance) });
825
+ verifyProvenance: (provenance) => verifyRuntimeAssetProvenance(state, provenance),
826
+ });
703
827
  return;
704
828
  }
705
- if (['GET', 'HEAD'].includes(request.method ?? '') && url.pathname === '/runtime/file-content') {
829
+ if (['GET', 'HEAD'].includes(request.method ?? '') &&
830
+ url.pathname === '/runtime/file-content') {
706
831
  if (writeDestroyed(state, response))
707
832
  return;
708
833
  const root = await workspaceRoot(state, url.searchParams.get('workspaceId') ?? undefined, directoryName);
@@ -738,8 +863,10 @@ async function dispatchRuntimeRequest(state, request, response) {
738
863
  }
739
864
  const controller = new AbortController();
740
865
  const abort = () => controller.abort();
741
- const closedRequest = () => { if (request.aborted || !request.complete)
742
- abort(); };
866
+ const closedRequest = () => {
867
+ if (request.aborted || !request.complete)
868
+ abort();
869
+ };
743
870
  request.once('aborted', abort);
744
871
  request.once('close', closedRequest);
745
872
  response.once('close', abort);
@@ -752,7 +879,8 @@ async function dispatchRuntimeRequest(state, request, response) {
752
879
  catch (error) {
753
880
  if (error instanceof Error && error.name === 'AbortError')
754
881
  return;
755
- if (error instanceof Error && ['invalid_workspace_search', 'invalid_workspace_search_cursor'].includes(error.message))
882
+ if (error instanceof Error &&
883
+ ['invalid_workspace_search', 'invalid_workspace_search_cursor'].includes(error.message))
756
884
  writeJson(response, 400, { error: error.message });
757
885
  else
758
886
  throw error;
@@ -820,7 +948,9 @@ async function dispatchRuntimeRequest(state, request, response) {
820
948
  catch (error) {
821
949
  if (error instanceof WorkspaceContextError)
822
950
  throw error;
823
- writeJson(response, 409, { error: error instanceof RemixReferenceError ? error.message : 'remix_reference_unavailable' });
951
+ writeJson(response, 409, {
952
+ error: error instanceof RemixReferenceError ? error.message : 'remix_reference_unavailable',
953
+ });
824
954
  }
825
955
  return;
826
956
  }
@@ -843,13 +973,17 @@ async function dispatchRuntimeRequest(state, request, response) {
843
973
  return;
844
974
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
845
975
  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)));
976
+ 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
977
  writeJson(response, 200, view);
848
978
  }
849
979
  catch (error) {
850
980
  if (error instanceof WorkspaceContextError)
851
981
  throw error;
852
- writeJson(response, 409, { error: error instanceof Error && error.message.startsWith('workspace_skills_') ? error.message : 'workspace_skills_unavailable' });
982
+ writeJson(response, 409, {
983
+ error: error instanceof Error && error.message.startsWith('workspace_skills_')
984
+ ? error.message
985
+ : 'workspace_skills_unavailable',
986
+ });
853
987
  }
854
988
  return;
855
989
  }
@@ -866,10 +1000,18 @@ async function dispatchRuntimeRequest(state, request, response) {
866
1000
  }
867
1001
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
868
1002
  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 });
1003
+ const first = await readWorkspaceLevelEditor(root, state.journal.fileRevisions(scope), {
1004
+ state,
1005
+ workspaceId,
1006
+ directoryName,
1007
+ });
870
1008
  for (const document of Object.values(first.definition?.documents ?? {}))
871
1009
  await withObservedFile(state, workspaceId, document.path, async () => undefined, directoryName);
872
- writeJson(response, 200, await readWorkspaceLevelEditor(root, state.journal.fileRevisions(scope), { state, workspaceId, directoryName }));
1010
+ writeJson(response, 200, await readWorkspaceLevelEditor(root, state.journal.fileRevisions(scope), {
1011
+ state,
1012
+ workspaceId,
1013
+ directoryName,
1014
+ }));
873
1015
  return;
874
1016
  }
875
1017
  if (request.method === 'GET' && url.pathname === '/runtime/level-editor/tasks') {
@@ -880,7 +1022,9 @@ async function dispatchRuntimeRequest(state, request, response) {
880
1022
  return;
881
1023
  }
882
1024
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
883
- writeJson(response, 200, { tasks: await listLevelEditorTasks(state, workspaceId, directoryName, await workspaceRoot(state, workspaceId, directoryName)) });
1025
+ writeJson(response, 200, {
1026
+ tasks: await listLevelEditorTasks(state, workspaceId, directoryName, await workspaceRoot(state, workspaceId, directoryName)),
1027
+ });
884
1028
  return;
885
1029
  }
886
1030
  if (request.method === 'GET' && url.pathname === '/runtime/skill-activation-requests') {
@@ -892,7 +1036,9 @@ async function dispatchRuntimeRequest(state, request, response) {
892
1036
  }
893
1037
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
894
1038
  await workspaceRoot(state, workspaceId, directoryName);
895
- writeJson(response, 200, { requests: await listSkillActivationRequests(state, workspaceId, directoryName) });
1039
+ writeJson(response, 200, {
1040
+ requests: await listSkillActivationRequests(state, workspaceId, directoryName),
1041
+ });
896
1042
  return;
897
1043
  }
898
1044
  if (request.method === 'GET' && url.pathname === '/runtime/level-editor/requests') {
@@ -904,7 +1050,9 @@ async function dispatchRuntimeRequest(state, request, response) {
904
1050
  }
905
1051
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
906
1052
  await workspaceRoot(state, workspaceId, directoryName);
907
- writeJson(response, 200, { requests: await listLevelEditorRequests(state, workspaceId, directoryName) });
1053
+ writeJson(response, 200, {
1054
+ requests: await listLevelEditorRequests(state, workspaceId, directoryName),
1055
+ });
908
1056
  return;
909
1057
  }
910
1058
  if (request.method === 'GET' && url.pathname === '/runtime/parameters') {
@@ -922,14 +1070,18 @@ async function dispatchRuntimeRequest(state, request, response) {
922
1070
  }
923
1071
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
924
1072
  const root = await workspaceRoot(state, workspaceId, directoryName);
925
- writeJson(response, 200, { tasks: await listParameterAgentTasks(state, workspaceId, directoryName, root) });
1073
+ writeJson(response, 200, {
1074
+ tasks: await listParameterAgentTasks(state, workspaceId, directoryName, root),
1075
+ });
926
1076
  return;
927
1077
  }
928
1078
  if (request.method === 'GET' && url.pathname === '/runtime/terminals') {
929
1079
  if (writeDestroyed(state, response))
930
1080
  return;
931
1081
  writeJson(response, 200, {
932
- terminals: state.journal.listTerminals(url.searchParams.get('workspaceId') ?? undefined).filter(terminal => (terminal.directoryName ?? 'basic') === directoryName),
1082
+ terminals: state.journal
1083
+ .listTerminals(url.searchParams.get('workspaceId') ?? undefined)
1084
+ .filter((terminal) => (terminal.directoryName ?? 'basic') === directoryName),
933
1085
  });
934
1086
  return;
935
1087
  }
@@ -957,7 +1109,9 @@ async function dispatchRuntimeRequest(state, request, response) {
957
1109
  directoryName,
958
1110
  sessionId: url.searchParams.get('sessionId') ?? undefined,
959
1111
  }));
960
- const observed = operation ? await observeLevelEditorTransaction(state, operation) : undefined;
1112
+ const observed = operation
1113
+ ? await observeLevelEditorTransaction(state, operation)
1114
+ : undefined;
961
1115
  writeJson(response, observed ? 200 : 404, observed ?? { error: 'operation_not_found' });
962
1116
  return;
963
1117
  }
@@ -971,8 +1125,9 @@ async function dispatchRuntimeRequest(state, request, response) {
971
1125
  return;
972
1126
  }
973
1127
  const operation = state.journal.getOperationById(operationLog[1]);
974
- if (!operation || operation.workspaceId !== workspaceId
975
- || (operation.directoryName ?? 'basic') !== directoryName) {
1128
+ if (!operation ||
1129
+ operation.workspaceId !== workspaceId ||
1130
+ (operation.directoryName ?? 'basic') !== directoryName) {
976
1131
  writeJson(response, 404, { error: 'operation_not_found' });
977
1132
  return;
978
1133
  }
@@ -996,13 +1151,16 @@ async function dispatchRuntimeRequest(state, request, response) {
996
1151
  return;
997
1152
  }
998
1153
  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]))) {
1154
+ const body = (await readJson(request));
1155
+ if (!body ||
1156
+ Object.keys(body).some((key) => !['bridge', 'threadId', 'turnId', 'toolCallId'].includes(key)) ||
1157
+ !['bridge', 'threadId', 'turnId', 'toolCallId'].every((key) => typeof body[key] === 'string' && Boolean(body[key]))) {
1002
1158
  writeJson(response, 400, { valid: false });
1003
1159
  return;
1004
1160
  }
1005
- writeJson(response, 200, { valid: state.native?.scheduledTaskProposalSource?.(body) === true });
1161
+ writeJson(response, 200, {
1162
+ valid: state.native?.scheduledTaskProposalSource?.(body) === true,
1163
+ });
1006
1164
  return;
1007
1165
  }
1008
1166
  if (request.method === 'POST' && url.pathname === '/runtime/operations') {
@@ -1018,13 +1176,17 @@ async function dispatchRuntimeRequest(state, request, response) {
1018
1176
  writeJson(response, 409, { error: 'directory_busy' });
1019
1177
  return;
1020
1178
  }
1021
- if (request.method === 'GET' && ['/runtime/connectivity', '/runtime/files'].includes(url.pathname)) {
1179
+ if (request.method === 'GET' &&
1180
+ ['/runtime/connectivity', '/runtime/files'].includes(url.pathname)) {
1022
1181
  const cause = error instanceof Error && error.cause ? error.cause : error;
1023
1182
  if (['EACCES', 'EPERM'].includes(String(cause?.code))) {
1024
1183
  writeJson(response, 409, { error: 'workspace_permission_denied' });
1025
1184
  return;
1026
1185
  }
1027
- if (url.pathname === '/runtime/connectivity' && error instanceof WorkspaceContextError && error.cause && !['ENOENT', 'ENOTDIR'].includes(String(cause?.code))) {
1186
+ if (url.pathname === '/runtime/connectivity' &&
1187
+ error instanceof WorkspaceContextError &&
1188
+ error.cause &&
1189
+ !['ENOENT', 'ENOTDIR'].includes(String(cause?.code))) {
1028
1190
  writeJson(response, 500, { error: 'workspace_access_failed' });
1029
1191
  return;
1030
1192
  }
@@ -1058,7 +1220,7 @@ function handshake(state) {
1058
1220
  if (item.name === 'conversation') {
1059
1221
  if (conversationReady)
1060
1222
  return capability('conversation', true);
1061
- return capability('conversation', false, destroyed ? 'runtime_destroyed' : state.nativeError ?? 'adapter_not_started');
1223
+ return capability('conversation', false, destroyed ? 'runtime_destroyed' : (state.nativeError ?? 'adapter_not_started'));
1062
1224
  }
1063
1225
  if (item.name === 'files') {
1064
1226
  return filesReady
@@ -1083,7 +1245,9 @@ function handshake(state) {
1083
1245
  if (destroyed)
1084
1246
  return capability('desktop', false, 'runtime_destroyed');
1085
1247
  const status = desktopHost(state).snapshot().status;
1086
- return status === 'ready' ? capability('desktop', true) : capability('desktop', false, status);
1248
+ return status === 'ready'
1249
+ ? capability('desktop', true)
1250
+ : capability('desktop', false, status);
1087
1251
  }
1088
1252
  if (item.name === 'parameters') {
1089
1253
  return destroyed
@@ -1142,8 +1306,15 @@ async function acceptOperation(state, request, response) {
1142
1306
  if (parsed.data.kind === 'workspace.references.import') {
1143
1307
  const source = workspaceRemixReferenceImportRequestSchema.safeParse(parsed.data.payload);
1144
1308
  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))) {
1309
+ if (!source.success ||
1310
+ (snapshot &&
1311
+ (snapshot.source.environment !== state.config.environment ||
1312
+ ('builtinTemplate' in source.data.source
1313
+ ? !('builtinTemplate' in snapshot.source) ||
1314
+ snapshot.source.builtinTemplate !== source.data.source.builtinTemplate
1315
+ : !('projectUuid' in snapshot.source) ||
1316
+ snapshot.source.projectUuid !== source.data.source.projectUuid ||
1317
+ snapshot.source.branchName !== source.data.source.branchName)))) {
1147
1318
  writeJson(response, 400, { error: 'invalid_remix_reference' });
1148
1319
  return;
1149
1320
  }
@@ -1152,13 +1323,20 @@ async function acceptOperation(state, request, response) {
1152
1323
  writeJson(response, 400, { error: 'invalid_operation', reason: 'sessionId_required' });
1153
1324
  return;
1154
1325
  }
1155
- if (parsed.data.directoryName === 'root' && !parsed.data.kind.startsWith('session.') && !['runtime.ensure', 'terminal.create'].includes(parsed.data.kind)) {
1326
+ if (parsed.data.directoryName === 'root' &&
1327
+ !parsed.data.kind.startsWith('session.') &&
1328
+ !['runtime.ensure', 'terminal.create'].includes(parsed.data.kind)) {
1156
1329
  writeJson(response, 400, { error: 'invalid_workspace_directory' });
1157
1330
  return;
1158
1331
  }
1159
- if (state.config.workspaceMode === 'multi' && parsed.data.kind === 'runtime.ensure' && parsed.data.workspaceId && parsed.data.directoryName !== 'root') {
1332
+ if (state.config.workspaceMode === 'multi' &&
1333
+ parsed.data.kind === 'runtime.ensure' &&
1334
+ parsed.data.workspaceId &&
1335
+ parsed.data.directoryName !== 'root') {
1160
1336
  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;
1337
+ const restoring = state.journal
1338
+ .getWorkspace(workspaceId)
1339
+ ?.directories?.some((directory) => directory.directoryName === directoryName && directory.status === 'reclaimed') ?? false;
1162
1340
  // Opening a resident checkout only refreshes instructions and can share its background install.
1163
1341
  // Rebuilding a reclaimed checkout must still exclude every existing writer.
1164
1342
  await withDirectoryWrite(state, { workspaceId, directoryName, mode: restoring ? 'exclusive' : 'shared', wait: false }, async () => {
@@ -1169,7 +1347,8 @@ async function acceptOperation(state, request, response) {
1169
1347
  await sessionWorkspaceRoot(state, parsed.data.workspaceId, parsed.data.directoryName);
1170
1348
  // Shared classification: a new conversation writes no files, so a batch hold must not refuse it.
1171
1349
  const occupiesDirectory = takesWorkspaceDirectory(parsed.data.kind);
1172
- if (occupiesDirectory && await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName)) {
1350
+ if (occupiesDirectory &&
1351
+ (await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName))) {
1173
1352
  writeJson(response, 409, { error: 'batch_workspace_readonly' });
1174
1353
  return;
1175
1354
  }
@@ -1178,15 +1357,23 @@ async function acceptOperation(state, request, response) {
1178
1357
  : parsed.data.kind === 'asset.library.import'
1179
1358
  ? { selection: parsed.data.payload, snapshot: parsed.data.libraryAssetImports }
1180
1359
  : parsed.data.kind === 'session.turn' && parsed.data.scheduledTaskProposalBridge
1181
- ? { ...asRecord(parsed.data.payload), scheduledTaskProposalBridge: parsed.data.scheduledTaskProposalBridge }
1360
+ ? {
1361
+ ...asRecord(parsed.data.payload),
1362
+ scheduledTaskProposalBridge: parsed.data.scheduledTaskProposalBridge,
1363
+ }
1182
1364
  : parsed.data.payload;
1183
1365
  const accept = async () => {
1184
1366
  const result = await acceptOperationCore(state, parsed.data, {
1185
1367
  leaseFence: incomingFence,
1186
1368
  executionPayload,
1187
1369
  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') };
1370
+ if (parsed.data.kind.startsWith('session.') &&
1371
+ parsed.data.sessionId &&
1372
+ productionSessionAttemptId(parsed.data.sessionId)) {
1373
+ return {
1374
+ status: 409,
1375
+ body: runtimeError('capability_unavailable', 'production_session_readonly'),
1376
+ };
1190
1377
  }
1191
1378
  if (parsed.data.kind === 'session.create') {
1192
1379
  const creation = state.journal.findSessionCreation(parsed.data.sessionId);
@@ -1198,7 +1385,10 @@ async function acceptOperation(state, request, response) {
1198
1385
  const payload = asRecord(parsed.data.payload);
1199
1386
  const terminalId = typeof payload.terminalId === 'string' ? payload.terminalId.trim() : '';
1200
1387
  if (!terminalId)
1201
- return { status: 400, body: { error: 'invalid_operation', reason: 'terminalId_required' } };
1388
+ return {
1389
+ status: 400,
1390
+ body: { error: 'invalid_operation', reason: 'terminalId_required' },
1391
+ };
1202
1392
  const creation = state.journal.findTerminalCreation(terminalId);
1203
1393
  const terminal = state.journal.getTerminal(terminalId);
1204
1394
  if (creation && operationScopeKey(creation) !== scopeKey) {
@@ -1207,16 +1397,20 @@ async function acceptOperation(state, request, response) {
1207
1397
  if (terminal && !creation)
1208
1398
  return { status: 409, body: { error: 'terminal_already_bound' } };
1209
1399
  }
1210
- else if (parsed.data.kind.startsWith('session.') && state.config.workspaceMode === 'multi') {
1400
+ else if (parsed.data.kind.startsWith('session.') &&
1401
+ state.config.workspaceMode === 'multi') {
1211
1402
  const session = state.journal.getSession(parsed.data.sessionId);
1212
1403
  // A replaced sandbox has no binding yet; the recovering turn creates it.
1213
1404
  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'))) {
1405
+ if (!rebinding &&
1406
+ (!session ||
1407
+ session.workspaceId !== parsed.data.workspaceId ||
1408
+ (session.directoryName ?? 'basic') !== (parsed.data.directoryName ?? 'basic'))) {
1216
1409
  return { status: 409, body: { error: 'session_workspace_mismatch' } };
1217
1410
  }
1218
1411
  }
1219
- if (parsed.data.kind === 'workspace.references.import' && !parsed.data.remixReferenceSnapshot) {
1412
+ if (parsed.data.kind === 'workspace.references.import' &&
1413
+ !parsed.data.remixReferenceSnapshot) {
1220
1414
  return { status: 400, body: { error: 'remix_reference_snapshot_required' } };
1221
1415
  }
1222
1416
  if (parsed.data.kind === 'asset.library.import' && !parsed.data.libraryAssetImports) {
@@ -1243,7 +1437,8 @@ async function acceptOperation(state, request, response) {
1243
1437
  writeJson(response, executed.status === 'failed' ? 409 : 202, executed);
1244
1438
  };
1245
1439
  const acceptBound = async () => {
1246
- if (occupiesDirectory && await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName)) {
1440
+ if (occupiesDirectory &&
1441
+ (await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName))) {
1247
1442
  writeJson(response, 409, { error: 'batch_workspace_readonly' });
1248
1443
  return;
1249
1444
  }
@@ -1319,7 +1514,7 @@ async function handleTerminalIdentity(state, request, response, remainder) {
1319
1514
  if (writeDestroyed(state, response))
1320
1515
  return;
1321
1516
  const [terminalId, action] = remainder.split('/');
1322
- if (!terminalId || action && action !== 'connect') {
1517
+ if (!terminalId || (action && action !== 'connect')) {
1323
1518
  writeJson(response, 404, { error: 'not_found' });
1324
1519
  return;
1325
1520
  }
@@ -1424,7 +1619,12 @@ async function readObservedParameters(state, workspaceId, directoryName = 'basic
1424
1619
  const scope = revisionScope(state, workspaceId, directoryName);
1425
1620
  return state.journal.exclusive(`workspace:${scope ?? 'single'}`, async () => {
1426
1621
  const first = await readWorkspaceParameters(root, state.journal.fileRevisions(scope));
1427
- for (const path of [first.schemaPath, first.dataPath, first.runtimeI18n?.globalPath, first.runtimeI18n?.themePath]) {
1622
+ for (const path of [
1623
+ first.schemaPath,
1624
+ first.dataPath,
1625
+ first.runtimeI18n?.globalPath,
1626
+ first.runtimeI18n?.themePath,
1627
+ ]) {
1428
1628
  if (path)
1429
1629
  await withObservedFile(state, workspaceId, path, async () => undefined, directoryName);
1430
1630
  }
@@ -1502,7 +1702,10 @@ async function handleSourceEvents(state, request, response, url) {
1502
1702
  const runtimeEpoch = epochQuery === null ? state.runtimeEpoch : Number(epochQuery);
1503
1703
  const runtimeId = url.searchParams.get('runtimeId') ?? state.config.runtimeId;
1504
1704
  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) {
1705
+ if (runtimeId !== state.config.runtimeId ||
1706
+ generation !== state.config.generation ||
1707
+ !Number.isInteger(runtimeEpoch) ||
1708
+ runtimeEpoch < 0) {
1506
1709
  writeJson(response, 403, { error: 'source_identity_mismatch' });
1507
1710
  return;
1508
1711
  }
@@ -1512,15 +1715,26 @@ async function handleSourceEvents(state, request, response, url) {
1512
1715
  runtimeEpoch,
1513
1716
  after: safeAfter,
1514
1717
  };
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 });
1718
+ if (!state.journal
1719
+ .epochsSnapshot()
1720
+ .some((epoch) => epoch.runtime_id === runtimeId &&
1721
+ epoch.generation === generation &&
1722
+ epoch.runtime_epoch === runtimeEpoch)) {
1723
+ writeJson(response, 409, {
1724
+ code: 'source_gap',
1725
+ retainedFrom: 1,
1726
+ highWater: 0,
1727
+ coveredThrough: safeAfter,
1728
+ });
1517
1729
  return;
1518
1730
  }
1519
1731
  if (String(request.headers.accept ?? '').includes('text/event-stream')) {
1520
1732
  writeSourceStream(state, request, response, query);
1521
1733
  return;
1522
1734
  }
1523
- if (epochQuery === null && !url.searchParams.has('runtimeId') && !url.searchParams.has('generation')) {
1735
+ if (epochQuery === null &&
1736
+ !url.searchParams.has('runtimeId') &&
1737
+ !url.searchParams.has('generation')) {
1524
1738
  const events = state.journal.listEvents(safeAfter);
1525
1739
  const page = state.journal.readSourcePage({ ...query, limit: 1 });
1526
1740
  writeJson(response, 200, {