@playcraft/cli 0.1.60-beta → 0.1.62-beta
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.
- package/dist/commands/build.js +16 -7
- package/dist/project-skills/builtin-import.js +2 -2
- package/dist/skill-registry/messages.js +2 -2
- package/dist/skill-registry/platform-package.js +2 -4
- package/dist/utils/agent-api-client.js +22 -0
- package/dist/workspace-runtime/codex/app-server.js +24 -1
- package/dist/workspace-runtime/codex/config-toml.js +9 -5
- package/dist/workspace-runtime/codex/delivery-tools.js +3 -10
- package/dist/workspace-runtime/codex/launcher-client.js +25 -13
- package/dist/workspace-runtime/codex/model-broker.js +13 -3
- package/dist/workspace-runtime/codex/production-skill-tools.js +95 -0
- package/dist/workspace-runtime/processes/skill-command.js +130 -0
- package/dist/workspace-runtime/serve.js +12 -1
- package/dist/workspace-runtime/server/accept-operation.js +10 -1
- package/dist/workspace-runtime/server/directory-access.js +11 -4
- package/dist/workspace-runtime/server/dispatch.js +70 -3
- package/dist/workspace-runtime/server/http.js +61 -3
- package/dist/workspace-runtime/server/operation-record.js +4 -1
- package/dist/workspace-runtime/workspaces/dependencies.js +35 -10
- package/dist/workspace-runtime/workspaces/developer-command.js +65 -64
- package/dist/workspace-runtime/workspaces/directories.js +4 -1
- package/dist/workspace-runtime/workspaces/empty-template.js +3 -1
- package/dist/workspace-runtime/workspaces/platform-export.js +3 -3
- package/dist/workspace-runtime/workspaces/procedural-3d/candidate.js +129 -0
- package/dist/workspace-runtime/workspaces/procedural-3d/contracts.js +34 -0
- package/dist/workspace-runtime/workspaces/procedural-3d/files.js +83 -0
- package/dist/workspace-runtime/workspaces/procedural-3d/host.js +595 -0
- package/dist/workspace-runtime/workspaces/procedural-3d/model-preview-build.js +184 -0
- package/dist/workspace-runtime/workspaces/procedural-3d/model-preview-recovery.js +77 -0
- package/dist/workspace-runtime/workspaces/procedural-3d/model-preview-store.js +170 -0
- package/dist/workspace-runtime/workspaces/procedural-3d/model-preview-worker.js +85 -0
- package/dist/workspace-runtime/workspaces/procedural-3d/read-candidates.js +219 -0
- package/dist/workspace-runtime/workspaces/procedural-3d/render-worker.js +159 -0
- package/dist/workspace-runtime/workspaces/procedural-3d/review-contracts.js +257 -0
- package/dist/workspace-runtime/workspaces/procedural-3d/review-page.js +26 -0
- package/dist/workspace-runtime/workspaces/skill-execution-context.js +69 -0
- package/dist/workspace-runtime/workspaces/skills.js +15 -5
- package/package.json +4 -3
- package/project-template-v2/.playcraft/skills.lock.json +7 -0
- package/project-templates/empty/AGENTS.md +11 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { realpath } from 'node:fs/promises';
|
|
2
|
+
import { PROCEDURAL_3D_SKILL_REF, resolveWorkspaceSkillExecutionPolicy, workspaceComposerSelectionSchema, } from '@playcraft/common/cloud-workspace/runtime';
|
|
3
|
+
import { assertOperationDirectoryAccess } from '../server/directory-access.js';
|
|
4
|
+
import { sessionWorkspaceRoot } from './registry.js';
|
|
5
|
+
import { readWorkspaceSkillSnapshot } from './skills.js';
|
|
6
|
+
/** Read-only preflight. It must precede the directory gate and cannot install or freeze files. */
|
|
7
|
+
export async function preflightSkillExecution(state, request) {
|
|
8
|
+
if (request.kind !== 'session.turn')
|
|
9
|
+
return undefined;
|
|
10
|
+
const body = request.payload && typeof request.payload === 'object'
|
|
11
|
+
? request.payload
|
|
12
|
+
: {};
|
|
13
|
+
if ('directoryAccessMode' in body || 'skillExecutionPolicyVersion' in body)
|
|
14
|
+
throw new Error('untrusted_runtime_field');
|
|
15
|
+
const selection = workspaceComposerSelectionSchema.safeParse({
|
|
16
|
+
context: body.context,
|
|
17
|
+
workMode: body.workMode,
|
|
18
|
+
});
|
|
19
|
+
if (!selection.success)
|
|
20
|
+
throw new Error('invalid_composer_context');
|
|
21
|
+
if (!selection.data.context?.skills.length)
|
|
22
|
+
return undefined;
|
|
23
|
+
if (request.directoryName === 'root')
|
|
24
|
+
throw new Error('context_directory_mismatch');
|
|
25
|
+
const root = await sessionWorkspaceRoot(state, request.workspaceId, request.directoryName);
|
|
26
|
+
return resolveWorkspaceSkillExecutionPolicy(await readWorkspaceSkillSnapshot(root), selection.data.context.skills);
|
|
27
|
+
}
|
|
28
|
+
/** Native tool callers supply the actual thread/turn binding, never shell-provided identity. */
|
|
29
|
+
export async function resolveActiveSkillExecutionContext(state, input) {
|
|
30
|
+
const skillRef = input.skillRef ?? PROCEDURAL_3D_SKILL_REF;
|
|
31
|
+
if (state.journal.destroyed())
|
|
32
|
+
throw new Error('skill_execution_identity_mismatch');
|
|
33
|
+
if (skillRef !== PROCEDURAL_3D_SKILL_REF)
|
|
34
|
+
throw new Error('context_skill_unavailable');
|
|
35
|
+
const session = state.journal.getSession(input.sessionId);
|
|
36
|
+
const matches = state.journal
|
|
37
|
+
.listOperations()
|
|
38
|
+
.map((item) => item.operation)
|
|
39
|
+
.filter((operation) => operation.kind === 'session.turn' &&
|
|
40
|
+
['dispatched', 'accepted'].includes(operation.status) &&
|
|
41
|
+
operation.sessionId === input.sessionId &&
|
|
42
|
+
operation.nativeThreadId === input.nativeThreadId &&
|
|
43
|
+
operation.nativeTurnId === input.nativeTurnId &&
|
|
44
|
+
operation.generation === state.config.generation &&
|
|
45
|
+
operation.runtimeEpoch === state.runtimeEpoch &&
|
|
46
|
+
operation.userId === state.config.userId &&
|
|
47
|
+
operation.environment === state.config.environment);
|
|
48
|
+
const operation = matches.length === 1 ? matches[0] : undefined;
|
|
49
|
+
if (!operation ||
|
|
50
|
+
!session ||
|
|
51
|
+
session.threadId !== input.nativeThreadId ||
|
|
52
|
+
(session.activeTurnId !== undefined && session.activeTurnId !== input.nativeTurnId) ||
|
|
53
|
+
session.workspaceId !== operation.workspaceId ||
|
|
54
|
+
(session.directoryName ?? 'basic') !== (operation.directoryName ?? 'basic'))
|
|
55
|
+
throw new Error('skill_execution_identity_mismatch');
|
|
56
|
+
if (operation.directoryAccessMode !== 'exclusive' || operation.skillExecutionPolicyVersion !== 1)
|
|
57
|
+
throw new Error('requires-exclusive-turn');
|
|
58
|
+
if (operation.workMode === 'plan' || operation.permissionMode === 'read-only')
|
|
59
|
+
throw new Error('skill_execution_readonly');
|
|
60
|
+
await assertOperationDirectoryAccess(state, operation, 'exclusive');
|
|
61
|
+
const root = await sessionWorkspaceRoot(state, operation.workspaceId, operation.directoryName);
|
|
62
|
+
if ((await realpath(root)) !== (await realpath(input.root)))
|
|
63
|
+
throw new Error('skill_execution_identity_mismatch');
|
|
64
|
+
const policy = resolveWorkspaceSkillExecutionPolicy(await readWorkspaceSkillSnapshot(root), operation.context?.skills ?? []);
|
|
65
|
+
const entry = policy.entries.find((item) => item.revision.skillRef === skillRef);
|
|
66
|
+
if (!entry || policy.directoryAccessMode !== 'exclusive')
|
|
67
|
+
throw new Error('requires-exclusive-turn');
|
|
68
|
+
return { operation, entry, lockHash: policy.lockHash, declarationHash: policy.declarationHash };
|
|
69
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { cp, lstat, open, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
3
4
|
import { basename, dirname, join } from 'node:path';
|
|
4
5
|
import { load } from 'js-yaml';
|
|
5
6
|
import { canonicalProjectSkillsJson, createCanonicalBundleArchive, effectiveProjectSkills, extractBundleArchive, inspectProjectSkills, readProjectSkillSource, readProjectSkillsLockFile, sourceTreeDigest, validateProjectSkillsSnapshot, } from '@playcraft/common/project-skills';
|
|
@@ -156,13 +157,22 @@ export async function freezeWorkspaceSkills(root, cache, loadArtifact) {
|
|
|
156
157
|
expectedFiles.add(relative);
|
|
157
158
|
await safeDirectory(generation, dirname(relative));
|
|
158
159
|
// shared
|
|
159
|
-
await writeFile(target, file.data, { flag: 'wx', mode: 0o444 }).catch(
|
|
160
|
+
await writeFile(target, file.data, { flag: 'wx', mode: 0o444 }).catch(error => {
|
|
160
161
|
if (error.code !== 'EEXIST')
|
|
161
162
|
throw error;
|
|
162
|
-
const stat = await lstat(target);
|
|
163
|
-
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || !file.data.equals(await readFile(target)))
|
|
164
|
-
throw new Error('workspace_skills_digest_mismatch');
|
|
165
163
|
});
|
|
164
|
+
const handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
165
|
+
try {
|
|
166
|
+
const stat = await handle.stat();
|
|
167
|
+
if (!stat.isFile() || stat.nlink !== 1 || !file.data.equals(await handle.readFile()))
|
|
168
|
+
throw new Error('workspace_skills_digest_mismatch');
|
|
169
|
+
// A private controller umask must not remove the developer's read access.
|
|
170
|
+
// Repair only the verified inode, keeping every frozen byte read-only.
|
|
171
|
+
await handle.chmod(0o444);
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
await handle.close();
|
|
175
|
+
}
|
|
166
176
|
}
|
|
167
177
|
const body = files.find(file => file.path === 'SKILL.md');
|
|
168
178
|
if (!body)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcraft/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.62-beta",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"@gltf-transform/core": "^4.3.0",
|
|
54
54
|
"@gltf-transform/extensions": "^4.3.0",
|
|
55
55
|
"@gltf-transform/functions": "^4.3.0",
|
|
56
|
-
"@playcraft/build": "0.1.
|
|
57
|
-
"@playcraft/common": "0.1.
|
|
56
|
+
"@playcraft/build": "0.1.62-beta",
|
|
57
|
+
"@playcraft/common": "0.1.62-beta",
|
|
58
58
|
"better-sqlite3-pinned": "npm:better-sqlite3@13.0.3",
|
|
59
59
|
"chokidar": "^4.0.3",
|
|
60
60
|
"commander": "^13.1.0",
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"@types/update-notifier": "^6.0.8",
|
|
92
92
|
"@types/ws": "^8.18.1",
|
|
93
93
|
"esbuild": "^0.28.1",
|
|
94
|
+
"three": "^0.183.2",
|
|
94
95
|
"typescript": "^5.9.3",
|
|
95
96
|
"vitest": "^3.2.4"
|
|
96
97
|
}
|
|
@@ -141,6 +141,13 @@
|
|
|
141
141
|
"surface": "sandbox-agent",
|
|
142
142
|
"mountPath": ".playcraft/skills/remix__playcraft-prefab"
|
|
143
143
|
},
|
|
144
|
+
{
|
|
145
|
+
"skillRef": "remix/playcraft-remix-references",
|
|
146
|
+
"version": "1.1.0",
|
|
147
|
+
"artifactHash": "32d4bb3bb6ed2dc0405997161dc65cd535559a71c227f0425612809ee5c178af",
|
|
148
|
+
"surface": "sandbox-agent",
|
|
149
|
+
"mountPath": ".playcraft/skills/remix__playcraft-remix-references"
|
|
150
|
+
},
|
|
144
151
|
{
|
|
145
152
|
"skillRef": "remix/playcraft-remix-workflow",
|
|
146
153
|
"version": "1.0.0",
|
|
@@ -42,6 +42,16 @@ Reference snapshots under `references/remixes/` are fixed source material. Their
|
|
|
42
42
|
|
|
43
43
|
Use the built-in CLI (Command Line Interface, 命令行界面) commands for platform assets. Run the relevant command's `--help` to inspect its supported parameters. The workspace supplies credentials; keep them out of commands, files, and chat output.
|
|
44
44
|
|
|
45
|
+
### Asset sourcing
|
|
46
|
+
|
|
47
|
+
Follow the user's explicit source choice: reuse specified assets or generate new assets when requested, without asking again. Otherwise, when a task needs images, audio, sprites, animation, models, or video, search the shared library before generating. Cover both authorized team assets and public assets. When the current team is known from verified workspace or project context, search it with `--team-id <teamId>` and search public assets separately with `--visibility public`; a team filter does not include public assets. Without verified team context, omit both filters to search the credential's authorized scopes rather than guessing a team identifier.
|
|
48
|
+
|
|
49
|
+
Inspect promising candidates with `library get` and `library files`, checking their intended use, style, dimensions, format, and required package contents against the task. Reuse suitable assets directly, preferring team assets when candidates are equally suitable, and download the complete file or package into the project's asset directory. Do not substitute a preview for the production files. If a narrow query finds nothing suitable, broaden the keywords or filters and inspect relevant result pages before concluding there is no match. Generate only the missing assets after these searches find no suitable candidate. A failed or unavailable search is not an empty result: report the limitation and recover the search instead of claiming that the library has no suitable assets.
|
|
50
|
+
|
|
51
|
+
Do not ask the user to choose reuse versus generation for every asset. Ask only when candidate differences materially affect the requested direction or suitability remains unclear; present the relevant candidates and tradeoffs. An execution without user interaction should pass that ambiguity to its coordinating conversation through the existing workflow. Report which assets were actually reused or generated and why.
|
|
52
|
+
|
|
53
|
+
### Commands
|
|
54
|
+
|
|
45
55
|
- Shared library: `playcraft library search "<query>" --json`, `library list`, and `library facets` support type, category, tags, team, and pagination filters. `library get <assetId>` reads metadata; `library files <assetId>` lists a complete package. These identifiers belong to the shared library, separate from project assets returned by `tools list-assets`.
|
|
46
56
|
- Download library content: `playcraft library download <assetId> --output <new-path>` saves a file or a complete package directory. `--output-dir <new-directory>` always saves a directory. Packages retain manifests, nested files, and relative dependencies. Download working assets into the project's asset directory; choose `dist/` for final deliverables. Existing output paths are preserved.
|
|
47
57
|
- Layer separation: `playcraft image decompose-layers --input <image> --output-dir <directory>` uses the current workspace layer pipeline and saves a base, separate layers, and metadata with stacking order and bounds. Optional `--prompt`, `--size`, and `--image-model` guide generation. `--engine qwen` selects the legacy pipeline.
|
|
@@ -55,7 +65,7 @@ The artifact panel shows final deliverables under `dist/` at the current project
|
|
|
55
65
|
|
|
56
66
|
When a generated asset is ready for delivery, save or copy it into `dist/` with a stable relative path. Keep the resources required by a model or document within `dist/` and preserve their relative links. Reuse the same delivery path for a revised artifact so its versions remain grouped; the platform retains its history. Use different paths for independent candidates or deliverables.
|
|
57
67
|
|
|
58
|
-
Run playable builds from the project root (`playcraft build --mode full --platform playcraft --format html .`). In a workspace this command builds in an isolated copy, validates the result, and retains the playable at `dist/playable/index.html` while preserving other deliverables. A successful agent command or turn that writes a valid self-contained HTML (HyperText Markup Language, 超文本标记语言) file at this same path is collected as the same playable preview. No separate publish action is required to view it. A base build's `dist/index.html` can still require resource files and remains a document. The page's generate-preview action rebuilds the current project through the same validation. Public
|
|
68
|
+
Run playable builds from the project root (`playcraft build --mode full --platform playcraft --format html .`). In a workspace this command builds in an isolated copy, validates the result, and retains the playable at `dist/playable/index.html` while preserving other deliverables. A successful agent command or turn that writes a valid self-contained HTML (HyperText Markup Language, 超文本标记语言) file at this same path is collected as the same playable preview. No separate publish action is required to view it. A base build's `dist/index.html` can still require resource files and remains a document. The page's generate-preview action rebuilds the current project through the same validation. After a successful build the command also publishes the playable and prints `Public URL: <address>`; when you report the build, link that address as the playable instead of the sandbox path, since it opens directly and can be shared. If the command prints `Public URL unavailable`, report that the build succeeded without a public address and give the reason it printed. Temporary `.playcraft-preview-out/` and `.playcraft-temp/` files are not deliverables. Preserve other artifacts already in `dist/` when rebuilding a playable.
|
|
59
69
|
|
|
60
70
|
Keep this convention in `AGENTS.md`, the project's standard instruction filename. Do not create a separate `AGENT.md`.
|
|
61
71
|
|