@playcraft/cli 0.1.72 → 0.1.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -109,6 +109,9 @@ export class RuntimeJournal {
|
|
|
109
109
|
getWorkspace(workspaceId) {
|
|
110
110
|
return this.snapshot.workspaces[workspaceId];
|
|
111
111
|
}
|
|
112
|
+
listWorkspaces() {
|
|
113
|
+
return Object.values(this.snapshot.workspaces);
|
|
114
|
+
}
|
|
112
115
|
async putWorkspace(binding) {
|
|
113
116
|
const parsed = workspaceRegistrationSchema.parse(binding);
|
|
114
117
|
await this.run('upsertRecord', { bucket: 'workspaces', key: parsed.workspaceId, body: parsed });
|
|
@@ -33,7 +33,7 @@ import { revisionScope } from './workspaces/registry.js';
|
|
|
33
33
|
import { observeWorkspaceArtifacts } from './workspaces/artifact-events.js';
|
|
34
34
|
import { loadWorkspaceMcpConfig } from './codex/mcp-config.js';
|
|
35
35
|
import { startSourceEventUploader, stopSourceEventUploader } from './server/source-uploader.js';
|
|
36
|
-
import { platformExportTarget, readPlatformExport, startPlatformExport } from './workspaces/platform-export.js';
|
|
36
|
+
import { platformExportTarget, readPlatformExport, startPlatformExport, syncWorkspaceBindings } from './workspaces/platform-export.js';
|
|
37
37
|
import { scheduledTaskProposalHandler } from './workspaces/scheduled-task-proposals.js';
|
|
38
38
|
import { executionContextToolHandler } from './workspaces/execution-context.js';
|
|
39
39
|
import { registerLevelEditorRequest, startLevelEditorRequestQueue, stopLevelEditorRequestQueue } from './workspaces/level-editor-requests.js';
|
|
@@ -352,6 +352,8 @@ export async function startWorkspaceRuntime(config, options = {}) {
|
|
|
352
352
|
const port = await listenOn(server, config.listen.host, config.listen.port);
|
|
353
353
|
startLevelEditorRequestQueue(state);
|
|
354
354
|
startSkillActivationRequestQueue(state);
|
|
355
|
+
// After listening: the platform registers a missed binding through this server before it replies.
|
|
356
|
+
void syncWorkspaceBindings(state);
|
|
355
357
|
return {
|
|
356
358
|
config: state.config,
|
|
357
359
|
state,
|
|
@@ -42,7 +42,7 @@ import { refreshWorkspaceArtifacts } from '../workspaces/artifact-events.js';
|
|
|
42
42
|
import { WorkspaceArtifactStore } from '../workspaces/artifacts.js';
|
|
43
43
|
import { OperationLogStore } from '../workspaces/operation-log.js';
|
|
44
44
|
import { acceptOperationCore, observeLevelEditorTransaction } from './accept-operation.js';
|
|
45
|
-
import { workspaceArtifactContentQuerySchema } from '@playcraft/common/cloud-workspace/runtime';
|
|
45
|
+
import { workspaceArtifactContentQuerySchema, workspaceBindingIdentity } from '@playcraft/common/cloud-workspace/runtime';
|
|
46
46
|
import { searchWorkspaceFiles } from '../workspaces/files-search.js';
|
|
47
47
|
import { serveWorkspaceArchive } from '../workspaces/archive.js';
|
|
48
48
|
import { verifyRuntimeAssetProvenance } from '../workspaces/asset-provenance-verifier.js';
|
|
@@ -251,6 +251,8 @@ async function dispatchRuntimeRequest(state, request, response) {
|
|
|
251
251
|
...(state.native?.startPrediction ? { predictionVersion: 1 } : {}),
|
|
252
252
|
sourceReadToken: state.journal.sourceReadToken(),
|
|
253
253
|
workspaceMode: state.config.workspaceMode,
|
|
254
|
+
// Runtime compares these with Business and registers again a binding this sandbox missed.
|
|
255
|
+
workspaceBindings: state.journal.listWorkspaces().map(workspaceBindingIdentity),
|
|
254
256
|
executionGroup: {
|
|
255
257
|
replaced: state.journal.executionGroup().replaced,
|
|
256
258
|
stopConfirmed: state.journal.executionGroup().stopConfirmed,
|
|
@@ -31,12 +31,11 @@ export async function platformExportTarget(state, scope) {
|
|
|
31
31
|
return { projectId: binding.projectId, root };
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
* first save does)
|
|
34
|
+
* Asks the platform about this workspace's binding. `promote` gives a standalone workspace a
|
|
35
|
+
* project (as its first save does); both register the binding Business holds again when this
|
|
36
|
+
* runtime lacks it. The registration reaches this runtime before the reply.
|
|
36
37
|
*/
|
|
37
|
-
async function
|
|
38
|
-
if (state.journal.getWorkspace(scope.workspaceId)?.targetKind !== 'standalone' || scope.directoryName !== 'basic')
|
|
39
|
-
return;
|
|
38
|
+
async function requestBinding(state, workspaceId, intent) {
|
|
40
39
|
const config = state.config;
|
|
41
40
|
const response = await fetch(platformEndpoint(config.publicBaseUrl, PLATFORM_ROUTES.workspacePromotions), {
|
|
42
41
|
method: 'POST',
|
|
@@ -44,7 +43,7 @@ async function promoteStandalone(state, scope) {
|
|
|
44
43
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
45
44
|
headers: { authorization: `Bearer ${config.controlToken}`, 'content-type': 'application/json', 'playcraft-env': config.environment },
|
|
46
45
|
body: JSON.stringify({ runtimeId: config.runtimeId, generation: config.generation, runtimeEpoch: state.runtimeEpoch,
|
|
47
|
-
workspaceId:
|
|
46
|
+
workspaceId, ...(intent === 'sync' ? { intent } : {}) }),
|
|
48
47
|
});
|
|
49
48
|
if (!response.ok) {
|
|
50
49
|
const body = await response.json().catch(() => undefined);
|
|
@@ -52,6 +51,28 @@ async function promoteStandalone(state, scope) {
|
|
|
52
51
|
}
|
|
53
52
|
await response.body?.cancel().catch(() => undefined);
|
|
54
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Basic needs a project to export from. Without one in the journal, the workspace is either still
|
|
56
|
+
* standalone or holds a binding whose registration never arrived; the platform settles both.
|
|
57
|
+
*/
|
|
58
|
+
async function ensureProjectBinding(state, scope) {
|
|
59
|
+
if (scope.directoryName !== 'basic' || state.journal.getWorkspace(scope.workspaceId)?.projectId)
|
|
60
|
+
return;
|
|
61
|
+
await requestBinding(state, scope.workspaceId, 'promote');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* On start, asks that each binding this runtime holds still match Business, so one whose
|
|
65
|
+
* registration never arrived is registered again before anything needs it. Best effort.
|
|
66
|
+
*/
|
|
67
|
+
export async function syncWorkspaceBindings(state) {
|
|
68
|
+
if (state.config.workspaceMode !== 'multi' || !state.config.publicBaseUrl || !state.config.controlToken)
|
|
69
|
+
return;
|
|
70
|
+
for (const binding of state.journal.listWorkspaces()) {
|
|
71
|
+
await requestBinding(state, binding.workspaceId, 'sync').catch((error) => {
|
|
72
|
+
console.warn('[workspace-runtime] binding sync failed:', error instanceof Error ? error.message : error);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
55
76
|
const credentialSchema = z.object({ apiUrl: z.string().url(), token: z.string().min(1) });
|
|
56
77
|
async function request(state, path, init) {
|
|
57
78
|
await ensureCliCredentials(state).catch(() => { throw new PlatformExportError('cli_credentials_unavailable'); });
|
|
@@ -86,13 +107,16 @@ async function request(state, path, init) {
|
|
|
86
107
|
* Saves the directory (commit + push), then exports that commit for the channels through the
|
|
87
108
|
* platform, as the delivery panel's Export does. Each channel uses that panel's fixed HTML or
|
|
88
109
|
* ZIP choice. Other options repeat the channel's latest export of the branch, or stay empty
|
|
89
|
-
* without one. A standalone workspace first becomes a project
|
|
110
|
+
* without one. A standalone workspace first becomes a project, and a binding this runtime missed
|
|
111
|
+
* is registered again.
|
|
90
112
|
*/
|
|
91
113
|
export async function startPlatformExport(state, input) {
|
|
92
|
-
await
|
|
114
|
+
await ensureProjectBinding(state, input);
|
|
93
115
|
const target = await platformExportTarget(state, input);
|
|
116
|
+
// The platform replied that the workspace has a project, but this runtime still holds none.
|
|
94
117
|
if (!target)
|
|
95
|
-
throw new PlatformExportError('
|
|
118
|
+
throw new PlatformExportError(input.directoryName === 'basic'
|
|
119
|
+
? 'workspace_registration_not_delivered' : 'platform_export_requires_project');
|
|
96
120
|
const saved = await submitTrustedOperation(state, {
|
|
97
121
|
kind: 'workspace.save',
|
|
98
122
|
workspaceId: input.workspaceId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcraft/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.73",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"@gltf-transform/core": "^4.3.0",
|
|
58
58
|
"@gltf-transform/extensions": "^4.3.0",
|
|
59
59
|
"@gltf-transform/functions": "^4.3.0",
|
|
60
|
-
"@playcraft/build": "0.1.
|
|
61
|
-
"@playcraft/common": "0.1.
|
|
60
|
+
"@playcraft/build": "0.1.73",
|
|
61
|
+
"@playcraft/common": "0.1.73",
|
|
62
62
|
"better-sqlite3-pinned": "npm:better-sqlite3@13.0.3",
|
|
63
63
|
"chokidar": "^4.0.3",
|
|
64
64
|
"commander": "^13.1.0",
|