@playcraft/cli 0.1.70 → 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.
Files changed (43) hide show
  1. package/README.md +20 -1
  2. package/dist/cli-root-help.js +1 -0
  3. package/dist/commands/references.js +3 -0
  4. package/dist/commands/skills.js +68 -4
  5. package/dist/commands/tools-batch.js +2 -0
  6. package/dist/project-skills/discovery.js +248 -0
  7. package/dist/project-skills/messages.js +80 -0
  8. package/dist/project-skills/remote-activation.js +500 -0
  9. package/dist/project-skills/remote-client.js +117 -0
  10. package/dist/project-skills/remote-commands.js +87 -0
  11. package/dist/project-skills/remote-prepare.js +223 -0
  12. package/dist/project-skills/search-command.js +47 -0
  13. package/dist/project-skills/search.js +96 -0
  14. package/dist/recording/project-digest.js +1 -1
  15. package/dist/references/messages.js +2 -0
  16. package/dist/references/protocol.js +39 -12
  17. package/dist/workspace-runtime/batch/freeze.js +94 -24
  18. package/dist/workspace-runtime/batch/records.js +12 -6
  19. package/dist/workspace-runtime/batch/routes.js +31 -11
  20. package/dist/workspace-runtime/batch/source-candidate.js +29 -16
  21. package/dist/workspace-runtime/batch/source-capture.js +251 -20
  22. package/dist/workspace-runtime/codex/app-server.js +30 -1
  23. package/dist/workspace-runtime/codex/model-broker.js +93 -6
  24. package/dist/workspace-runtime/codex/remix-references-broker.js +208 -67
  25. package/dist/workspace-runtime/codex/skill-discovery-broker.js +174 -0
  26. package/dist/workspace-runtime/serve.js +5 -0
  27. package/dist/workspace-runtime/server/directory-access.js +10 -0
  28. package/dist/workspace-runtime/server/dispatch.js +121 -28
  29. package/dist/workspace-runtime/server/http.js +345 -117
  30. package/dist/workspace-runtime/server/operation-record.js +3 -1
  31. package/dist/workspace-runtime/workspaces/composer-context.js +92 -41
  32. package/dist/workspace-runtime/workspaces/files.js +59 -23
  33. package/dist/workspace-runtime/workspaces/remix-references.js +162 -53
  34. package/dist/workspace-runtime/workspaces/skill-activation-recovery.js +54 -0
  35. package/dist/workspace-runtime/workspaces/skill-activation-requests.js +512 -0
  36. package/dist/workspace-runtime/workspaces/skills.js +13 -176
  37. package/dist/workspace-runtime/workspaces/starter-template.js +74 -0
  38. package/package.json +4 -3
  39. package/project-template/.claude/skills/playcraft-skill-recommender/SKILL.md +28 -6
  40. package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +28 -6
  41. package/project-templates/empty/gitignore +1 -0
  42. package/project-templates/phaser/.claude/skills/playcraft-skill-recommender/SKILL.md +28 -6
  43. package/project-templates/threejs/.claude/skills/playcraft-skill-recommender/SKILL.md +28 -6
@@ -1,40 +1,41 @@
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';
15
15
  import { readWorkspaceLevelEditor } from '../workspaces/level-editor.js';
16
16
  import { listLevelEditorTasks } from '../workspaces/level-editor-agent.js';
17
17
  import { listLevelEditorRequests } from '../workspaces/level-editor-requests.js';
18
+ import { listSkillActivationRequests } from '../workspaces/skill-activation-requests.js';
18
19
  import { readDependencies } from '../workspaces/dependencies.js';
19
20
  import { permissionDriftFromError } from '../workspaces/workspace-shares.js';
20
21
  import { desktopHost, desktopRoute } from './desktop-host.js';
21
22
  import { ensureCliCredentials } from '../workspaces/cli-credentials.js';
22
- import { readAttachmentUpload, uploadWorkspaceAttachment, uploadWorkspaceVideoAttachment } from '../workspaces/attachments.js';
23
+ import { readAttachmentUpload, uploadWorkspaceAttachment, uploadWorkspaceVideoAttachment, } from '../workspaces/attachments.js';
23
24
  import { serveWorkspaceAttachment } from '../workspaces/attachment-content.js';
24
25
  import { serveWorkspaceFileContent } from '../workspaces/file-content.js';
25
26
  import { validateDeliveryArtifact, DeliveryValidationError } from '../workspaces/delivery.js';
26
27
  import { DeliveryStore } from '../workspaces/delivery-store.js';
27
- import { importWorkspaceLibraryAttachment, listWorkspaceLibraryAttachments, listWorkspaceLibraryImportReceipts, readLibraryAttachmentUpload } from '../workspaces/library-attachments.js';
28
- 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';
29
30
  import { readWorkspaceSkills, readWorkspaceSkillSnapshot } from '../workspaces/skills.js';
30
- import { listWorkspaceRemixReferences, RemixReferenceError } from '../workspaces/remix-references.js';
31
+ import { listWorkspaceRemixReferences, RemixReferenceError, } from '../workspaces/remix-references.js';
31
32
  import { loadWorkspaceSkillArtifact } from '../workspaces/skill-artifact-loader.js';
32
33
  import { join } from 'node:path';
33
34
  import { terminalHost } from './terminal-host.js';
34
35
  import { batchDirectoryHeld } from '../batch/occupancy.js';
35
36
  import { dispatchBatchRoute } from '../batch/routes.js';
36
- import { cleanupWorkspaceDirectories, syncWorkspaceDirectories } from '../workspaces/directory-maintenance.js';
37
- import { registerWorkspaceDirectory, restoreWorkspaceDirectory } from '../workspaces/directories.js';
37
+ import { cleanupWorkspaceDirectories, syncWorkspaceDirectories, } from '../workspaces/directory-maintenance.js';
38
+ import { registerWorkspaceDirectory, restoreWorkspaceDirectory, } from '../workspaces/directories.js';
38
39
  import { readWorkspaceDirectoryStats } from '../workspaces/directory-stats.js';
39
40
  import { probeRuntimeConnectivity } from './connectivity.js';
40
41
  import { refreshWorkspaceArtifacts } from '../workspaces/artifact-events.js';
@@ -51,7 +52,7 @@ import { serveSessionImage } from '../workspaces/session-image.js';
51
52
  import { restoreNativeSession, SessionRestoreError } from './session-restore.js';
52
53
  import { handleAssetTransferRequest } from './asset-transfers.js';
53
54
  import { readWorktreeSource, registerChatWorktree } from '../workspaces/worktrees.js';
54
- import { withDirectoryWrite } from './directory-access.js';
55
+ import { directoryBusyCode, withDirectoryWrite } from './directory-access.js';
55
56
  import { readProceduralModelCandidates } from '../workspaces/procedural-3d/read-candidates.js';
56
57
  import { ModelPreviewStore } from '../workspaces/procedural-3d/model-preview-store.js';
57
58
  import { readModelPreparation } from '../workspaces/model-preparation.js';
@@ -60,23 +61,40 @@ import { handleAudioAssetRequest } from './audio-assets.js';
60
61
  export async function handleRuntimeRequest(state, request, response) {
61
62
  const url = new URL(request.url ?? '/', 'http://127.0.0.1');
62
63
  const workspaceId = url.searchParams.get('workspaceId');
63
- 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');
64
78
  if (authorizeRuntime(state, request) === 'control' && protectedRead) {
79
+ const directoryName = url.searchParams.get('directoryName') ?? 'basic';
65
80
  try {
66
- const directoryName = url.searchParams.get('directoryName') ?? 'basic';
67
81
  const readContext = { state, workspaceId: workspaceId ?? undefined, directoryName };
68
82
  const root = await workspaceRoot(state, readContext.workspaceId, directoryName);
69
83
  if (await hasPendingLevelEditorTransaction(root, readContext)) {
70
84
  // A retained writer owns the exclusive lease. Reconcile that exact operation before admitting a reader.
71
85
  for (const { operation } of state.journal.listOperations())
72
- 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)
73
90
  await observeLevelEditorTransaction(state, operation);
74
91
  }
75
92
  const recovering = await hasPendingLevelEditorTransaction(root, readContext);
76
93
  return await withDirectoryWrite(state, { ...readContext, mode: recovering ? 'exclusive' : 'shared', wait: false }, async () => {
77
94
  if (recovering) {
78
95
  const snapshot = await readWorkspaceLevelEditor(root, state.journal.fileRevisions(revisionScope(state, readContext.workspaceId, directoryName)), readContext);
79
- 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)))
80
98
  throw new Error(snapshot.diagnostics[0].code);
81
99
  }
82
100
  await assertNoPendingLevelEditorTransaction(root, readContext);
@@ -84,26 +102,49 @@ export async function handleRuntimeRequest(state, request, response) {
84
102
  });
85
103
  }
86
104
  catch (error) {
87
- if (!response.headersSent)
88
- 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
+ }
89
115
  return;
90
116
  }
91
117
  }
92
118
  // Body-scoped operations, members and terminal control take their own lifetime-aware leases.
93
- 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/') ||
94
121
  url.pathname === '/runtime/audio/export' ||
95
- 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) ||
96
124
  /^\/runtime\/sessions\/[^/]+\/prediction$/.test(url.pathname);
97
- if (authorizeRuntime(state, request) === 'control' && workspaceId &&
98
- (!['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) {
99
130
  try {
100
- 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,
101
135
  sessionId: url.searchParams.get('sessionId') ?? undefined,
102
- ...(url.pathname === '/runtime/git-bundle' ? { mode: 'exclusive' } : {}) }, () => dispatchRuntimeRequest(state, request, response));
136
+ ...(url.pathname === '/runtime/git-bundle' ? { mode: 'exclusive' } : {}),
137
+ }, () => dispatchRuntimeRequest(state, request, response));
103
138
  }
104
139
  catch (error) {
105
- if (!response.headersSent)
106
- 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
+ }
107
148
  else
108
149
  response.destroy(error instanceof Error ? error : undefined);
109
150
  return;
@@ -159,7 +200,7 @@ async function dispatchRuntimeRequest(state, request, response) {
159
200
  }
160
201
  const workspaceId = parsed.data;
161
202
  const root = await sessionWorkspaceRoot(state, workspaceId, directoryName);
162
- const rootStat = await stat(root).catch(error => {
203
+ const rootStat = await stat(root).catch((error) => {
163
204
  if (['ENOENT', 'ENOTDIR'].includes(error.code))
164
205
  throw new WorkspaceContextError('workspace_not_prepared', { cause: error });
165
206
  throw error;
@@ -191,6 +232,7 @@ async function dispatchRuntimeRequest(state, request, response) {
191
232
  parameterAgentVersion: 1,
192
233
  directoryConcurrencyVersion: 1,
193
234
  skillExecutionPolicyVersion: 1,
235
+ skillActivationVersion: 1,
194
236
  parameterInitializationVersion: 1,
195
237
  levelEditorVersion: 1,
196
238
  levelEditorTaskVersion: 1,
@@ -200,6 +242,7 @@ async function dispatchRuntimeRequest(state, request, response) {
200
242
  levelEditorRetryVersion: 1,
201
243
  remixReferencesVersion: 1,
202
244
  batchSourceFreezeRevision: 2,
245
+ batchStarterTemplateVersion: 1,
203
246
  batchSourcePublicationVersion: 1,
204
247
  batchReferenceContextVersion: 1,
205
248
  ...(state.native?.workspaceUi ? { workspaceUiVersion: 1 } : {}),
@@ -211,7 +254,9 @@ async function dispatchRuntimeRequest(state, request, response) {
211
254
  executionGroup: {
212
255
  replaced: state.journal.executionGroup().replaced,
213
256
  stopConfirmed: state.journal.executionGroup().stopConfirmed,
214
- ...(state.journal.stoppedRuntimeEpochs().length ? { stoppedRuntimeEpochs: state.journal.stoppedRuntimeEpochs() } : {}),
257
+ ...(state.journal.stoppedRuntimeEpochs().length
258
+ ? { stoppedRuntimeEpochs: state.journal.stoppedRuntimeEpochs() }
259
+ : {}),
215
260
  },
216
261
  });
217
262
  return;
@@ -228,8 +273,12 @@ async function dispatchRuntimeRequest(state, request, response) {
228
273
  writeJson(response, 409, { error: 'multi_workspace_unavailable' });
229
274
  return;
230
275
  }
231
- const scope = workspaceSessionRestoreResponseSchema.pick({ sessionId: true, workspaceId: true, directoryName: true }).safeParse({
232
- 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,
233
282
  });
234
283
  const parsed = workspaceSessionRestoreRequestSchema.safeParse(await readJson(request, 4096));
235
284
  if (!scope.success || !parsed.success) {
@@ -243,8 +292,11 @@ async function dispatchRuntimeRequest(state, request, response) {
243
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)));
244
293
  });
245
294
  writeJson(response, 200, workspaceSessionRestoreResponseSchema.parse({
246
- ...scope.data, threadId: restored.threadId, generation: parsed.data.generation,
247
- 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',
248
300
  }));
249
301
  }
250
302
  catch (error) {
@@ -265,8 +317,10 @@ async function dispatchRuntimeRequest(state, request, response) {
265
317
  return;
266
318
  }
267
319
  const session = state.journal.getSession(sessionId);
268
- if (!session || productionSessionAttemptId(sessionId) || session.workspaceId !== url.searchParams.get('workspaceId')
269
- || (session.directoryName ?? 'basic') !== directoryName) {
320
+ if (!session ||
321
+ productionSessionAttemptId(sessionId) ||
322
+ session.workspaceId !== url.searchParams.get('workspaceId') ||
323
+ (session.directoryName ?? 'basic') !== directoryName) {
270
324
  writeJson(response, 404, { error: 'session_not_found' });
271
325
  return;
272
326
  }
@@ -284,8 +338,10 @@ async function dispatchRuntimeRequest(state, request, response) {
284
338
  return;
285
339
  const sessionId = decodeURIComponent(predictionRoute[1]);
286
340
  const session = state.journal.getSession(sessionId);
287
- if (!session || productionSessionAttemptId(sessionId) || session.workspaceId !== url.searchParams.get('workspaceId')
288
- || (session.directoryName ?? 'basic') !== directoryName) {
341
+ if (!session ||
342
+ productionSessionAttemptId(sessionId) ||
343
+ session.workspaceId !== url.searchParams.get('workspaceId') ||
344
+ (session.directoryName ?? 'basic') !== directoryName) {
289
345
  writeJson(response, 404, { error: 'session_not_found' });
290
346
  return;
291
347
  }
@@ -303,7 +359,9 @@ async function dispatchRuntimeRequest(state, request, response) {
303
359
  if (writeDestroyed(state, response))
304
360
  return;
305
361
  const parsed = workspaceRegistrationRequestSchema.safeParse(await readJson(request));
306
- 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) {
307
365
  writeJson(response, 409, { error: 'invalid_workspace_registration' });
308
366
  return;
309
367
  }
@@ -317,7 +375,9 @@ async function dispatchRuntimeRequest(state, request, response) {
317
375
  if (writeDestroyed(state, response))
318
376
  return;
319
377
  const body = asRecord(await readJson(request));
320
- 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)) {
321
381
  writeJson(response, 409, { error: 'invalid_workspace_registration' });
322
382
  return;
323
383
  }
@@ -329,7 +389,12 @@ async function dispatchRuntimeRequest(state, request, response) {
329
389
  return;
330
390
  }
331
391
  const directory = workspaceDirectoryRegistrationSchema.parse(body.directory);
332
- 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))));
333
398
  }
334
399
  else {
335
400
  const directory = workspaceDirectoryRegistrationSchema.parse(body.directory);
@@ -337,17 +402,27 @@ async function dispatchRuntimeRequest(state, request, response) {
337
402
  }
338
403
  return;
339
404
  }
340
- if (request.method === 'POST' && url.pathname === '/runtime/workspaces/directories/worktree-source') {
405
+ if (request.method === 'POST' &&
406
+ url.pathname === '/runtime/workspaces/directories/worktree-source') {
341
407
  if (writeDestroyed(state, response))
342
408
  return;
343
409
  const body = asRecord(await readJson(request));
344
410
  const source = workspaceDirectoryNameSchema.safeParse(body.sourceDirectoryName);
345
- if (body.generation !== state.config.generation || body.runtimeEpoch !== state.runtimeEpoch || !Number.isSafeInteger(body.leaseFence)
346
- || !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') {
347
417
  writeJson(response, 409, { error: 'invalid_workspace_registration' });
348
418
  return;
349
419
  }
350
- 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)));
351
426
  return;
352
427
  }
353
428
  const maintenance = /^\/runtime\/workspaces\/([a-f0-9-]{36})\/directories\/(cleanup|sync|stats)$/.exec(url.pathname);
@@ -385,11 +460,15 @@ async function dispatchRuntimeRequest(state, request, response) {
385
460
  await handleSessionHistory(state, response, historyPath.workspaceId, historyPath.sessionId);
386
461
  return;
387
462
  }
388
- 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') {
389
465
  let recordedPath;
390
466
  if (request.method === 'POST') {
391
467
  const body = asRecord(await readJson(request));
392
- 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) {
393
472
  writeJson(response, 404, { error: 'image_not_found' });
394
473
  return;
395
474
  }
@@ -408,15 +487,18 @@ async function dispatchRuntimeRequest(state, request, response) {
408
487
  await handleSourceEvents(state, request, response, url);
409
488
  return;
410
489
  }
411
- if (url.pathname === '/runtime/attachments/library' && ['GET', 'POST'].includes(request.method ?? '')) {
490
+ if (url.pathname === '/runtime/attachments/library' &&
491
+ ['GET', 'POST'].includes(request.method ?? '')) {
412
492
  if (writeDestroyed(state, response))
413
493
  return;
414
494
  const workspaceId = url.searchParams.get('workspaceId') ?? '';
415
495
  const root = await workspaceRoot(state, workspaceId, directoryName);
416
496
  if (request.method === 'GET') {
417
497
  const sessionId = url.searchParams.get('sessionId') ?? undefined;
418
- writeJson(response, 200, { attachments: await listWorkspaceLibraryAttachments(root, sessionId),
419
- importReceipts: await listWorkspaceLibraryImportReceipts(root, sessionId) });
498
+ writeJson(response, 200, {
499
+ attachments: await listWorkspaceLibraryAttachments(root, sessionId),
500
+ importReceipts: await listWorkspaceLibraryImportReceipts(root, sessionId),
501
+ });
420
502
  }
421
503
  else {
422
504
  if (await batchDirectoryHeld(state, workspaceId, directoryName)) {
@@ -426,15 +508,25 @@ async function dispatchRuntimeRequest(state, request, response) {
426
508
  try {
427
509
  const attachment = await importWorkspaceLibraryAttachment(root, await readLibraryAttachmentUpload(request));
428
510
  await state.journal.allocateSourceEvent({
429
- runtimeId: state.config.runtimeId, generation: state.config.generation, runtimeEpoch: state.runtimeEpoch,
430
- 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(),
431
517
  payload: { workspaceId, directoryName, attachmentId: attachment.id },
432
518
  });
433
519
  writeJson(response, 200, attachment);
434
520
  }
435
521
  catch (error) {
436
- const code = error instanceof Error && ['library_asset_too_large', 'library_asset_changed', 'library_attachment_limit'].includes(error.message)
437
- ? 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';
438
530
  writeJson(response, 400, { error: code });
439
531
  }
440
532
  }
@@ -459,8 +551,12 @@ async function dispatchRuntimeRequest(state, request, response) {
459
551
  else if (uploadId && action === 'complete' && request.method === 'POST') {
460
552
  const attachment = await completeWorkspaceDirectoryAttachment(root, uploadId);
461
553
  await state.journal.allocateSourceEvent({
462
- runtimeId: state.config.runtimeId, generation: state.config.generation, runtimeEpoch: state.runtimeEpoch,
463
- 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(),
464
560
  payload: { workspaceId, directoryName, attachmentId: attachment.id },
465
561
  });
466
562
  writeJson(response, 200, attachment);
@@ -471,7 +567,11 @@ async function dispatchRuntimeRequest(state, request, response) {
471
567
  writeJson(response, 400, { error: 'invalid_attachment' });
472
568
  }
473
569
  catch (error) {
474
- 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
+ });
475
575
  }
476
576
  return;
477
577
  }
@@ -485,10 +585,16 @@ async function dispatchRuntimeRequest(state, request, response) {
485
585
  }
486
586
  const root = await workspaceRoot(state, workspaceId, directoryName);
487
587
  try {
488
- 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
+ }));
489
591
  }
490
592
  catch (error) {
491
- 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
+ });
492
598
  }
493
599
  return;
494
600
  }
@@ -509,7 +615,8 @@ async function dispatchRuntimeRequest(state, request, response) {
509
615
  }
510
616
  return;
511
617
  }
512
- if (['GET', 'HEAD'].includes(request.method ?? '') && url.pathname === '/runtime/attachment-content') {
618
+ if (['GET', 'HEAD'].includes(request.method ?? '') &&
619
+ url.pathname === '/runtime/attachment-content') {
513
620
  if (writeDestroyed(state, response))
514
621
  return;
515
622
  const root = await workspaceRoot(state, url.searchParams.get('workspaceId') ?? '', directoryName);
@@ -556,8 +663,7 @@ async function dispatchRuntimeRequest(state, request, response) {
556
663
  return;
557
664
  }
558
665
  await workspaceRoot(state, workspaceId, directoryName);
559
- const items = await new DeliveryStore(state.config.stateDir, workspaceId)
560
- .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);
561
667
  writeJson(response, 200, deliveryBuildListSchema.parse({ items, scannedAt: new Date().toISOString() }));
562
668
  return;
563
669
  }
@@ -592,7 +698,8 @@ async function dispatchRuntimeRequest(state, request, response) {
592
698
  writeJson(response, 200, deliveryBuildDetailSchema.parse({ record, ...(report ? { report } : {}) }));
593
699
  return;
594
700
  }
595
- 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') {
596
703
  if (writeDestroyed(state, response))
597
704
  return;
598
705
  const workspaceId = url.searchParams.get('workspaceId');
@@ -603,10 +710,15 @@ async function dispatchRuntimeRequest(state, request, response) {
603
710
  const root = await workspaceRoot(state, workspaceId, directoryName);
604
711
  try {
605
712
  const output = await new ModelPreviewStore(state.config.stateDir, root).content(url.searchParams.get('previewId') ?? '');
606
- response.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'content-length': output.bytes.length,
607
- '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,
608
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'",
609
- 'referrer-policy': 'no-referrer' });
720
+ 'referrer-policy': 'no-referrer',
721
+ });
610
722
  response.end(request.method === 'HEAD' ? undefined : output.bytes);
611
723
  }
612
724
  catch {
@@ -661,10 +773,15 @@ async function dispatchRuntimeRequest(state, request, response) {
661
773
  }
662
774
  await workspaceRoot(state, workspaceId, directoryName);
663
775
  const parsed = workspaceArtifactContentQuerySchema.safeParse({
664
- artifactId: url.searchParams.get('artifactId'), versionId: url.searchParams.get('versionId'),
776
+ artifactId: url.searchParams.get('artifactId'),
777
+ versionId: url.searchParams.get('versionId'),
665
778
  ...(url.searchParams.has('path') ? { path: url.searchParams.get('path') } : {}),
666
- ...(url.searchParams.has('offset') ? { offset: Number(url.searchParams.get('offset')) } : {}),
667
- ...(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
+ : {}),
668
785
  });
669
786
  if (!parsed.success) {
670
787
  writeJson(response, 400, { error: 'invalid_artifact_query' });
@@ -690,17 +807,27 @@ async function dispatchRuntimeRequest(state, request, response) {
690
807
  return;
691
808
  const archiveWorkspaceId = url.searchParams.get('workspaceId') ?? undefined;
692
809
  const root = await sessionWorkspaceRoot(state, archiveWorkspaceId, directoryName);
693
- const expectedModelPaths = state.journal.listOperations().map(({ operation }) => operation).filter(operation => operation.kind === 'asset.library.import'
694
- && (state.config.workspaceMode !== 'multi' || operation.workspaceId === archiveWorkspaceId)
695
- && (operation.directoryName ?? 'basic') === directoryName)
696
- .flatMap(operation => operation.result && 'bundleReceipt' in operation.result ? operation.result.bundleReceipt?.files ?? [] : [])
697
- .map(file => file.path).filter(path => path.toLowerCase().endsWith('.glb'));
698
- 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],
699
824
  expectedModelPaths: [...new Set(expectedModelPaths)],
700
- verifyProvenance: provenance => verifyRuntimeAssetProvenance(state, provenance) });
825
+ verifyProvenance: (provenance) => verifyRuntimeAssetProvenance(state, provenance),
826
+ });
701
827
  return;
702
828
  }
703
- if (['GET', 'HEAD'].includes(request.method ?? '') && url.pathname === '/runtime/file-content') {
829
+ if (['GET', 'HEAD'].includes(request.method ?? '') &&
830
+ url.pathname === '/runtime/file-content') {
704
831
  if (writeDestroyed(state, response))
705
832
  return;
706
833
  const root = await workspaceRoot(state, url.searchParams.get('workspaceId') ?? undefined, directoryName);
@@ -736,8 +863,10 @@ async function dispatchRuntimeRequest(state, request, response) {
736
863
  }
737
864
  const controller = new AbortController();
738
865
  const abort = () => controller.abort();
739
- const closedRequest = () => { if (request.aborted || !request.complete)
740
- abort(); };
866
+ const closedRequest = () => {
867
+ if (request.aborted || !request.complete)
868
+ abort();
869
+ };
741
870
  request.once('aborted', abort);
742
871
  request.once('close', closedRequest);
743
872
  response.once('close', abort);
@@ -750,7 +879,8 @@ async function dispatchRuntimeRequest(state, request, response) {
750
879
  catch (error) {
751
880
  if (error instanceof Error && error.name === 'AbortError')
752
881
  return;
753
- 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))
754
884
  writeJson(response, 400, { error: error.message });
755
885
  else
756
886
  throw error;
@@ -818,7 +948,9 @@ async function dispatchRuntimeRequest(state, request, response) {
818
948
  catch (error) {
819
949
  if (error instanceof WorkspaceContextError)
820
950
  throw error;
821
- 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
+ });
822
954
  }
823
955
  return;
824
956
  }
@@ -841,13 +973,17 @@ async function dispatchRuntimeRequest(state, request, response) {
841
973
  return;
842
974
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
843
975
  try {
844
- 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)));
845
977
  writeJson(response, 200, view);
846
978
  }
847
979
  catch (error) {
848
980
  if (error instanceof WorkspaceContextError)
849
981
  throw error;
850
- 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
+ });
851
987
  }
852
988
  return;
853
989
  }
@@ -864,10 +1000,18 @@ async function dispatchRuntimeRequest(state, request, response) {
864
1000
  }
865
1001
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
866
1002
  const root = await workspaceRoot(state, workspaceId, directoryName), scope = revisionScope(state, workspaceId, directoryName);
867
- 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
+ });
868
1008
  for (const document of Object.values(first.definition?.documents ?? {}))
869
1009
  await withObservedFile(state, workspaceId, document.path, async () => undefined, directoryName);
870
- 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
+ }));
871
1015
  return;
872
1016
  }
873
1017
  if (request.method === 'GET' && url.pathname === '/runtime/level-editor/tasks') {
@@ -878,7 +1022,23 @@ async function dispatchRuntimeRequest(state, request, response) {
878
1022
  return;
879
1023
  }
880
1024
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
881
- 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
+ });
1028
+ return;
1029
+ }
1030
+ if (request.method === 'GET' && url.pathname === '/runtime/skill-activation-requests') {
1031
+ if (writeDestroyed(state, response))
1032
+ return;
1033
+ if (directoryName === 'root') {
1034
+ writeJson(response, 400, { error: 'invalid_workspace_directory' });
1035
+ return;
1036
+ }
1037
+ const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
1038
+ await workspaceRoot(state, workspaceId, directoryName);
1039
+ writeJson(response, 200, {
1040
+ requests: await listSkillActivationRequests(state, workspaceId, directoryName),
1041
+ });
882
1042
  return;
883
1043
  }
884
1044
  if (request.method === 'GET' && url.pathname === '/runtime/level-editor/requests') {
@@ -890,7 +1050,9 @@ async function dispatchRuntimeRequest(state, request, response) {
890
1050
  }
891
1051
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
892
1052
  await workspaceRoot(state, workspaceId, directoryName);
893
- writeJson(response, 200, { requests: await listLevelEditorRequests(state, workspaceId, directoryName) });
1053
+ writeJson(response, 200, {
1054
+ requests: await listLevelEditorRequests(state, workspaceId, directoryName),
1055
+ });
894
1056
  return;
895
1057
  }
896
1058
  if (request.method === 'GET' && url.pathname === '/runtime/parameters') {
@@ -908,14 +1070,18 @@ async function dispatchRuntimeRequest(state, request, response) {
908
1070
  }
909
1071
  const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
910
1072
  const root = await workspaceRoot(state, workspaceId, directoryName);
911
- writeJson(response, 200, { tasks: await listParameterAgentTasks(state, workspaceId, directoryName, root) });
1073
+ writeJson(response, 200, {
1074
+ tasks: await listParameterAgentTasks(state, workspaceId, directoryName, root),
1075
+ });
912
1076
  return;
913
1077
  }
914
1078
  if (request.method === 'GET' && url.pathname === '/runtime/terminals') {
915
1079
  if (writeDestroyed(state, response))
916
1080
  return;
917
1081
  writeJson(response, 200, {
918
- 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),
919
1085
  });
920
1086
  return;
921
1087
  }
@@ -943,7 +1109,9 @@ async function dispatchRuntimeRequest(state, request, response) {
943
1109
  directoryName,
944
1110
  sessionId: url.searchParams.get('sessionId') ?? undefined,
945
1111
  }));
946
- const observed = operation ? await observeLevelEditorTransaction(state, operation) : undefined;
1112
+ const observed = operation
1113
+ ? await observeLevelEditorTransaction(state, operation)
1114
+ : undefined;
947
1115
  writeJson(response, observed ? 200 : 404, observed ?? { error: 'operation_not_found' });
948
1116
  return;
949
1117
  }
@@ -957,8 +1125,9 @@ async function dispatchRuntimeRequest(state, request, response) {
957
1125
  return;
958
1126
  }
959
1127
  const operation = state.journal.getOperationById(operationLog[1]);
960
- if (!operation || operation.workspaceId !== workspaceId
961
- || (operation.directoryName ?? 'basic') !== directoryName) {
1128
+ if (!operation ||
1129
+ operation.workspaceId !== workspaceId ||
1130
+ (operation.directoryName ?? 'basic') !== directoryName) {
962
1131
  writeJson(response, 404, { error: 'operation_not_found' });
963
1132
  return;
964
1133
  }
@@ -982,13 +1151,16 @@ async function dispatchRuntimeRequest(state, request, response) {
982
1151
  return;
983
1152
  }
984
1153
  if (request.method === 'POST' && url.pathname === '/runtime/scheduled-task-proposals/source') {
985
- const body = await readJson(request);
986
- if (!body || Object.keys(body).some(key => !['bridge', 'threadId', 'turnId', 'toolCallId'].includes(key))
987
- || !['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]))) {
988
1158
  writeJson(response, 400, { valid: false });
989
1159
  return;
990
1160
  }
991
- writeJson(response, 200, { valid: state.native?.scheduledTaskProposalSource?.(body) === true });
1161
+ writeJson(response, 200, {
1162
+ valid: state.native?.scheduledTaskProposalSource?.(body) === true,
1163
+ });
992
1164
  return;
993
1165
  }
994
1166
  if (request.method === 'POST' && url.pathname === '/runtime/operations') {
@@ -1004,13 +1176,17 @@ async function dispatchRuntimeRequest(state, request, response) {
1004
1176
  writeJson(response, 409, { error: 'directory_busy' });
1005
1177
  return;
1006
1178
  }
1007
- if (request.method === 'GET' && ['/runtime/connectivity', '/runtime/files'].includes(url.pathname)) {
1179
+ if (request.method === 'GET' &&
1180
+ ['/runtime/connectivity', '/runtime/files'].includes(url.pathname)) {
1008
1181
  const cause = error instanceof Error && error.cause ? error.cause : error;
1009
1182
  if (['EACCES', 'EPERM'].includes(String(cause?.code))) {
1010
1183
  writeJson(response, 409, { error: 'workspace_permission_denied' });
1011
1184
  return;
1012
1185
  }
1013
- 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))) {
1014
1190
  writeJson(response, 500, { error: 'workspace_access_failed' });
1015
1191
  return;
1016
1192
  }
@@ -1044,7 +1220,7 @@ function handshake(state) {
1044
1220
  if (item.name === 'conversation') {
1045
1221
  if (conversationReady)
1046
1222
  return capability('conversation', true);
1047
- 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'));
1048
1224
  }
1049
1225
  if (item.name === 'files') {
1050
1226
  return filesReady
@@ -1069,7 +1245,9 @@ function handshake(state) {
1069
1245
  if (destroyed)
1070
1246
  return capability('desktop', false, 'runtime_destroyed');
1071
1247
  const status = desktopHost(state).snapshot().status;
1072
- return status === 'ready' ? capability('desktop', true) : capability('desktop', false, status);
1248
+ return status === 'ready'
1249
+ ? capability('desktop', true)
1250
+ : capability('desktop', false, status);
1073
1251
  }
1074
1252
  if (item.name === 'parameters') {
1075
1253
  return destroyed
@@ -1128,8 +1306,15 @@ async function acceptOperation(state, request, response) {
1128
1306
  if (parsed.data.kind === 'workspace.references.import') {
1129
1307
  const source = workspaceRemixReferenceImportRequestSchema.safeParse(parsed.data.payload);
1130
1308
  const snapshot = parsed.data.remixReferenceSnapshot;
1131
- if (!source.success || (snapshot && (snapshot.source.projectUuid !== source.data.source.projectUuid ||
1132
- 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)))) {
1133
1318
  writeJson(response, 400, { error: 'invalid_remix_reference' });
1134
1319
  return;
1135
1320
  }
@@ -1138,13 +1323,20 @@ async function acceptOperation(state, request, response) {
1138
1323
  writeJson(response, 400, { error: 'invalid_operation', reason: 'sessionId_required' });
1139
1324
  return;
1140
1325
  }
1141
- 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)) {
1142
1329
  writeJson(response, 400, { error: 'invalid_workspace_directory' });
1143
1330
  return;
1144
1331
  }
1145
- 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') {
1146
1336
  const workspaceId = parsed.data.workspaceId, directoryName = parsed.data.directoryName ?? 'basic';
1147
- 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;
1148
1340
  // Opening a resident checkout only refreshes instructions and can share its background install.
1149
1341
  // Rebuilding a reclaimed checkout must still exclude every existing writer.
1150
1342
  await withDirectoryWrite(state, { workspaceId, directoryName, mode: restoring ? 'exclusive' : 'shared', wait: false }, async () => {
@@ -1155,7 +1347,8 @@ async function acceptOperation(state, request, response) {
1155
1347
  await sessionWorkspaceRoot(state, parsed.data.workspaceId, parsed.data.directoryName);
1156
1348
  // Shared classification: a new conversation writes no files, so a batch hold must not refuse it.
1157
1349
  const occupiesDirectory = takesWorkspaceDirectory(parsed.data.kind);
1158
- if (occupiesDirectory && await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName)) {
1350
+ if (occupiesDirectory &&
1351
+ (await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName))) {
1159
1352
  writeJson(response, 409, { error: 'batch_workspace_readonly' });
1160
1353
  return;
1161
1354
  }
@@ -1164,15 +1357,23 @@ async function acceptOperation(state, request, response) {
1164
1357
  : parsed.data.kind === 'asset.library.import'
1165
1358
  ? { selection: parsed.data.payload, snapshot: parsed.data.libraryAssetImports }
1166
1359
  : parsed.data.kind === 'session.turn' && parsed.data.scheduledTaskProposalBridge
1167
- ? { ...asRecord(parsed.data.payload), scheduledTaskProposalBridge: parsed.data.scheduledTaskProposalBridge }
1360
+ ? {
1361
+ ...asRecord(parsed.data.payload),
1362
+ scheduledTaskProposalBridge: parsed.data.scheduledTaskProposalBridge,
1363
+ }
1168
1364
  : parsed.data.payload;
1169
1365
  const accept = async () => {
1170
1366
  const result = await acceptOperationCore(state, parsed.data, {
1171
1367
  leaseFence: incomingFence,
1172
1368
  executionPayload,
1173
1369
  validate: async (scopeKey) => {
1174
- if (parsed.data.kind.startsWith('session.') && parsed.data.sessionId && productionSessionAttemptId(parsed.data.sessionId)) {
1175
- 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
+ };
1176
1377
  }
1177
1378
  if (parsed.data.kind === 'session.create') {
1178
1379
  const creation = state.journal.findSessionCreation(parsed.data.sessionId);
@@ -1184,7 +1385,10 @@ async function acceptOperation(state, request, response) {
1184
1385
  const payload = asRecord(parsed.data.payload);
1185
1386
  const terminalId = typeof payload.terminalId === 'string' ? payload.terminalId.trim() : '';
1186
1387
  if (!terminalId)
1187
- return { status: 400, body: { error: 'invalid_operation', reason: 'terminalId_required' } };
1388
+ return {
1389
+ status: 400,
1390
+ body: { error: 'invalid_operation', reason: 'terminalId_required' },
1391
+ };
1188
1392
  const creation = state.journal.findTerminalCreation(terminalId);
1189
1393
  const terminal = state.journal.getTerminal(terminalId);
1190
1394
  if (creation && operationScopeKey(creation) !== scopeKey) {
@@ -1193,16 +1397,20 @@ async function acceptOperation(state, request, response) {
1193
1397
  if (terminal && !creation)
1194
1398
  return { status: 409, body: { error: 'terminal_already_bound' } };
1195
1399
  }
1196
- else if (parsed.data.kind.startsWith('session.') && state.config.workspaceMode === 'multi') {
1400
+ else if (parsed.data.kind.startsWith('session.') &&
1401
+ state.config.workspaceMode === 'multi') {
1197
1402
  const session = state.journal.getSession(parsed.data.sessionId);
1198
1403
  // A replaced sandbox has no binding yet; the recovering turn creates it.
1199
1404
  const rebinding = !session && parsed.data.kind === 'session.turn' && parsed.data.sessionRecovery;
1200
- if (!rebinding && (!session || session.workspaceId !== parsed.data.workspaceId
1201
- || (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'))) {
1202
1409
  return { status: 409, body: { error: 'session_workspace_mismatch' } };
1203
1410
  }
1204
1411
  }
1205
- if (parsed.data.kind === 'workspace.references.import' && !parsed.data.remixReferenceSnapshot) {
1412
+ if (parsed.data.kind === 'workspace.references.import' &&
1413
+ !parsed.data.remixReferenceSnapshot) {
1206
1414
  return { status: 400, body: { error: 'remix_reference_snapshot_required' } };
1207
1415
  }
1208
1416
  if (parsed.data.kind === 'asset.library.import' && !parsed.data.libraryAssetImports) {
@@ -1229,7 +1437,8 @@ async function acceptOperation(state, request, response) {
1229
1437
  writeJson(response, executed.status === 'failed' ? 409 : 202, executed);
1230
1438
  };
1231
1439
  const acceptBound = async () => {
1232
- if (occupiesDirectory && await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName)) {
1440
+ if (occupiesDirectory &&
1441
+ (await batchDirectoryHeld(state, parsed.data.workspaceId, parsed.data.directoryName))) {
1233
1442
  writeJson(response, 409, { error: 'batch_workspace_readonly' });
1234
1443
  return;
1235
1444
  }
@@ -1305,7 +1514,7 @@ async function handleTerminalIdentity(state, request, response, remainder) {
1305
1514
  if (writeDestroyed(state, response))
1306
1515
  return;
1307
1516
  const [terminalId, action] = remainder.split('/');
1308
- if (!terminalId || action && action !== 'connect') {
1517
+ if (!terminalId || (action && action !== 'connect')) {
1309
1518
  writeJson(response, 404, { error: 'not_found' });
1310
1519
  return;
1311
1520
  }
@@ -1410,7 +1619,12 @@ async function readObservedParameters(state, workspaceId, directoryName = 'basic
1410
1619
  const scope = revisionScope(state, workspaceId, directoryName);
1411
1620
  return state.journal.exclusive(`workspace:${scope ?? 'single'}`, async () => {
1412
1621
  const first = await readWorkspaceParameters(root, state.journal.fileRevisions(scope));
1413
- 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
+ ]) {
1414
1628
  if (path)
1415
1629
  await withObservedFile(state, workspaceId, path, async () => undefined, directoryName);
1416
1630
  }
@@ -1488,7 +1702,10 @@ async function handleSourceEvents(state, request, response, url) {
1488
1702
  const runtimeEpoch = epochQuery === null ? state.runtimeEpoch : Number(epochQuery);
1489
1703
  const runtimeId = url.searchParams.get('runtimeId') ?? state.config.runtimeId;
1490
1704
  const generation = Number(url.searchParams.get('generation') ?? state.config.generation);
1491
- 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) {
1492
1709
  writeJson(response, 403, { error: 'source_identity_mismatch' });
1493
1710
  return;
1494
1711
  }
@@ -1498,15 +1715,26 @@ async function handleSourceEvents(state, request, response, url) {
1498
1715
  runtimeEpoch,
1499
1716
  after: safeAfter,
1500
1717
  };
1501
- if (!state.journal.epochsSnapshot().some(epoch => epoch.runtime_id === runtimeId && epoch.generation === generation && epoch.runtime_epoch === runtimeEpoch)) {
1502
- 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
+ });
1503
1729
  return;
1504
1730
  }
1505
1731
  if (String(request.headers.accept ?? '').includes('text/event-stream')) {
1506
1732
  writeSourceStream(state, request, response, query);
1507
1733
  return;
1508
1734
  }
1509
- 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')) {
1510
1738
  const events = state.journal.listEvents(safeAfter);
1511
1739
  const page = state.journal.readSourcePage({ ...query, limit: 1 });
1512
1740
  writeJson(response, 200, {