@playcraft/cli 0.1.23 → 0.1.24
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/README.md +3 -38
- package/dist/agent/local-backend.js +1 -1
- package/dist/cli-root-help.js +0 -1
- package/dist/commands/build.js +4 -9
- package/dist/commands/create.js +33 -26
- package/dist/commands/image.js +6 -2
- package/dist/commands/login.js +42 -84
- package/dist/commands/skills.js +0 -2
- package/dist/commands/tools-generation.js +4 -2
- package/dist/commands/tools.js +0 -2
- package/dist/config.js +4 -59
- package/dist/index.js +1 -6
- package/dist/remix/clone.js +0 -2
- package/dist/remix/init-template.js +0 -2
- package/dist/remix/pull.js +0 -2
- package/dist/remix/push.js +1 -21
- package/dist/utils/agent-api-client.js +29 -148
- package/dist/utils/playcraft-http-client.js +4 -6
- package/package.json +2 -8
- package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +61 -23
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +61 -23
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +61 -23
- package/dist/auth/account-activation.js +0 -157
- package/dist/auth/messages.js +0 -30
- package/dist/commands/platform-skills.generated.js +0 -19
- package/dist/commands/tools-batch.js +0 -59
- package/dist/commands/workspace-runtime.js +0 -54
- package/dist/project-skills/commands.js +0 -72
- package/dist/project-skills/lifecycle.js +0 -28
- package/dist/project-skills/local-run.js +0 -112
- package/dist/project-skills/local-store.js +0 -149
- package/dist/project-skills/messages.js +0 -72
- package/dist/project-skills/reconcile.js +0 -62
- package/dist/project-skills/remote-cache.js +0 -51
- package/dist/project-skills/validation.js +0 -19
- package/dist/utils/environment-endpoints.js +0 -17
- package/dist/utils/tool-operation-journal.js +0 -124
- package/dist/workspace-runtime/codex/app-server.js +0 -574
- package/dist/workspace-runtime/codex/config-toml.js +0 -108
- package/dist/workspace-runtime/codex/jsonrpc-stdio.js +0 -123
- package/dist/workspace-runtime/codex/launcher-client.js +0 -163
- package/dist/workspace-runtime/codex/loopback.js +0 -143
- package/dist/workspace-runtime/codex/model-broker.js +0 -239
- package/dist/workspace-runtime/codex/native-adapter.js +0 -9
- package/dist/workspace-runtime/main.js +0 -10
- package/dist/workspace-runtime/persistence/journal.js +0 -355
- package/dist/workspace-runtime/persistence/sqlite-client.js +0 -61
- package/dist/workspace-runtime/persistence/sqlite-worker.js +0 -1015
- package/dist/workspace-runtime/previews/publish.js +0 -259
- package/dist/workspace-runtime/previews/single-file.js +0 -43
- package/dist/workspace-runtime/processes/managed-writes.js +0 -69
- package/dist/workspace-runtime/production/contracts.js +0 -17
- package/dist/workspace-runtime/production/controller.js +0 -207
- package/dist/workspace-runtime/production/dependencies.js +0 -60
- package/dist/workspace-runtime/production/native.js +0 -70
- package/dist/workspace-runtime/production/processes.js +0 -86
- package/dist/workspace-runtime/production/save-command.js +0 -20
- package/dist/workspace-runtime/production/stages.js +0 -175
- package/dist/workspace-runtime/production/storage.js +0 -32
- package/dist/workspace-runtime/serve.js +0 -199
- package/dist/workspace-runtime/server/auth.js +0 -21
- package/dist/workspace-runtime/server/connectivity.js +0 -201
- package/dist/workspace-runtime/server/desktop-host.js +0 -429
- package/dist/workspace-runtime/server/desktop-preview-config.js +0 -81
- package/dist/workspace-runtime/server/desktop-preview-service.js +0 -157
- package/dist/workspace-runtime/server/desktop-preview-static.js +0 -53
- package/dist/workspace-runtime/server/desktop-preview.js +0 -189
- package/dist/workspace-runtime/server/dispatch.js +0 -848
- package/dist/workspace-runtime/server/endpoints.js +0 -19
- package/dist/workspace-runtime/server/execution-group.js +0 -85
- package/dist/workspace-runtime/server/http.js +0 -919
- package/dist/workspace-runtime/server/lock.js +0 -53
- package/dist/workspace-runtime/server/rfb-watch-gate.js +0 -168
- package/dist/workspace-runtime/server/terminal-host.js +0 -294
- package/dist/workspace-runtime/server/types.js +0 -1
- package/dist/workspace-runtime/workspaces/attachments.js +0 -109
- package/dist/workspace-runtime/workspaces/batch-commit.js +0 -142
- package/dist/workspace-runtime/workspaces/cli-credentials.js +0 -111
- package/dist/workspace-runtime/workspaces/context-error.js +0 -2
- package/dist/workspace-runtime/workspaces/dependencies.js +0 -264
- package/dist/workspace-runtime/workspaces/developer-command.js +0 -64
- package/dist/workspace-runtime/workspaces/directories.js +0 -106
- package/dist/workspace-runtime/workspaces/directory-access.js +0 -20
- package/dist/workspace-runtime/workspaces/directory-maintenance.js +0 -111
- package/dist/workspace-runtime/workspaces/directory-stats.js +0 -49
- package/dist/workspace-runtime/workspaces/empty-template.js +0 -32
- package/dist/workspace-runtime/workspaces/files.js +0 -113
- package/dist/workspace-runtime/workspaces/git-credential-broker.js +0 -312
- package/dist/workspace-runtime/workspaces/git-credential-helper.js +0 -68
- package/dist/workspace-runtime/workspaces/git-direct-store.js +0 -155
- package/dist/workspace-runtime/workspaces/git.js +0 -471
- package/dist/workspace-runtime/workspaces/json5-edit.js +0 -170
- package/dist/workspace-runtime/workspaces/parameters.js +0 -272
- package/dist/workspace-runtime/workspaces/prepare.js +0 -263
- package/dist/workspace-runtime/workspaces/registry.js +0 -192
- package/dist/workspace-runtime/workspaces/revisions.js +0 -31
- package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +0 -46
- package/dist/workspace-runtime/workspaces/skills.js +0 -179
- package/project-template-v2/.claude/agents/artist.md +0 -82
- package/project-template-v2/.claude/agents/developer.md +0 -153
- package/project-template-v2/.claude/agents/game-designer.md +0 -264
- package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +0 -533
- package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +0 -153
- package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +0 -27
- package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +0 -208
- package/project-template-v2/.claude/agents/refs/atom-skill-library.md +0 -81
- package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +0 -432
- package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +0 -252
- package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +0 -266
- package/project-template-v2/.claude/agents/refs/framework-mda.md +0 -147
- package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +0 -123
- package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +0 -88
- package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +0 -67
- package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +0 -120
- package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +0 -300
- package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +0 -495
- package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +0 -1141
- package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +0 -111
- package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +0 -365
- package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +0 -412
- package/project-template-v2/.claude/agents/reviewer.md +0 -75
- package/project-template-v2/.claude/agents/technical-artist.md +0 -86
- package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +0 -243
- package/project-template-v2/.claude/hooks/user-prompt.mjs +0 -133
- package/project-template-v2/.claude/settings.json +0 -33
- package/project-template-v2/.claude/skills/brainstorming/SKILL.md +0 -161
- package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +0 -270
- package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +0 -177
- package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +0 -354
- package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +0 -148
- package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +0 -56
- package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +0 -62
- package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
- package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +0 -309
- package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +0 -182
- package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +0 -123
- package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +0 -205
- package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +0 -280
- package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +0 -184
- package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +0 -306
- package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +0 -298
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +0 -123
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +0 -254
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +0 -324
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +0 -97
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +0 -118
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +0 -137
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +0 -397
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +0 -296
- package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +0 -122
- package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +0 -219
- package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +0 -373
- package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +0 -161
- package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +0 -215
- package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +0 -382
- package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +0 -423
- package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +0 -158
- package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +0 -100
- package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +0 -167
- package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +0 -270
- package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +0 -236
- package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +0 -130
- package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +0 -390
- package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +0 -23
- package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +0 -39
- package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +0 -87
- package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +0 -240
- package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +0 -226
- package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +0 -308
- package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +0 -275
- package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +0 -134
- package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +0 -142
- package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +0 -153
- package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +0 -386
- package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +0 -182
- package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +0 -72
- package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +0 -121
- package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -102
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +0 -127
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +0 -106
- package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +0 -128
- package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +0 -67
- package/project-template-v2/.claude/snapshot.mjs +0 -263
- package/project-template-v2/.playcraft/skills.lock.json +0 -152
- package/project-template-v2/CLAUDE.md +0 -146
- package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
- package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
- package/project-template-v2/assets/bundles/.gitkeep +0 -0
- package/project-template-v2/assets/images/bg/.gitkeep +0 -0
- package/project-template-v2/assets/images/reference/.gitkeep +0 -0
- package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
- package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
- package/project-template-v2/assets/images/ui/.gitkeep +0 -0
- package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
- package/project-template-v2/assets/models/.gitkeep +0 -0
- package/project-template-v2/docs/harness/iteration-1/context-flow.md +0 -254
- package/project-template-v2/docs/harness/iteration-1/generate-flow.md +0 -91
- package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +0 -214
- package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +0 -75
- package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +0 -63
- package/project-template-v2/docs/harness/iteration-2/context-flow.md +0 -223
- package/project-template-v2/docs/harness/iteration-2/generate-flow.md +0 -129
- package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +0 -267
- package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +0 -164
- package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +0 -115
- package/project-template-v2/docs/harness/orchestrator-flow.md +0 -364
- package/project-template-v2/docs/project-state.json +0 -60
- package/project-template-v2/docs/project-state.md +0 -72
- package/project-template-v2/docs/standards/README.md +0 -225
- package/project-template-v2/docs/standards/agent-behavior-standards.md +0 -174
- package/project-template-v2/docs/standards/artifacts/design-brief.md +0 -19
- package/project-template-v2/docs/standards/artifacts/design.md +0 -22
- package/project-template-v2/docs/standards/artifacts/game-code.md +0 -41
- package/project-template-v2/docs/standards/artifacts/todo-list.md +0 -41
- package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +0 -343
- package/project-template-v2/game/index.ts +0 -18
- package/project-template-v2/globals.d.ts +0 -51
- package/project-template-v2/index.css +0 -34
- package/project-template-v2/index.html +0 -18
- package/project-template-v2/main.ts +0 -9
- package/project-template-v2/package.json +0 -46
- package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +0 -31
- package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +0 -86
- package/project-template-v2/skills/_shared/scripts/dod-check.mjs +0 -153
- package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +0 -70
- package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +0 -91
- package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +0 -131
- package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +0 -80
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +0 -90
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +0 -93
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +0 -23
- package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +0 -230
- package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +0 -58
- package/project-template-v2/skills/_shared/scripts/state-get.mjs +0 -339
- package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +0 -39
- package/project-template-v2/skills/_shared/scripts/state-set.mjs +0 -94
- package/project-template-v2/skills/_shared/scripts/state-store.mjs +0 -783
- package/project-template-v2/skills/_shared/scripts/todo-add.mjs +0 -88
- package/project-template-v2/skills/_shared/scripts/todo-get.mjs +0 -130
- package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +0 -33
- package/project-template-v2/skills/_shared/scripts/todo-set.mjs +0 -47
- package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +0 -268
- package/project-template-v2/skills/_shared/scripts/verify-env.mjs +0 -285
- package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +0 -161
- package/project-template-v2/skills/playable-autoplay/SKILL.md +0 -176
- package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +0 -4
- package/project-template-v2/skills/playable-debug/SKILL.md +0 -116
- package/project-template-v2/skills/playable-debug/agents/openai.yaml +0 -4
- package/project-template-v2/skills/playable-debug/references/debug-config.md +0 -40
- package/project-template-v2/skills/playable-record/SKILL.md +0 -140
- package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +0 -104
- package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +0 -141
- package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +0 -425
- package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +0 -261
- package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +0 -398
- package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +0 -322
- package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +0 -105
- package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +0 -230
- package/project-template-v2/skills/playable-validate/SKILL.md +0 -61
- package/project-template-v2/skills/playable-validate/validation-rules.md +0 -14
- package/project-template-v2/skills/playable-verify-ui/SKILL.md +0 -63
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +0 -1
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +0 -218
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +0 -51
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +0 -59
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +0 -106
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +0 -116
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +0 -59
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +0 -64
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +0 -58
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +0 -333
- package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
- package/project-template-v2/tsconfig.json +0 -20
- package/project-template-v2/vite.config.ts +0 -27
- package/project-templates/batch-locks/phaser.yaml +0 -922
- package/project-templates/batch-locks/threejs.yaml +0 -978
- package/project-templates/empty/AGENTS.md +0 -23
- package/project-templates/empty/gitignore +0 -31
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
|
-
import { createCodexAppServerAdapter } from '../codex/app-server.js';
|
|
3
|
-
import { verifyProductionLease } from './storage.js';
|
|
4
|
-
import { processBirth, stopProductionGroup } from './processes.js';
|
|
5
|
-
export async function runProductionNative(state, input, root, record, track) {
|
|
6
|
-
if (!input.model)
|
|
7
|
-
throw new Error('production_model_configuration_required');
|
|
8
|
-
const groups = [];
|
|
9
|
-
const adapter = createCodexAppServerAdapter({ config: { ...state.config, workspaceMode: 'single', workspaceRoot: root },
|
|
10
|
-
command: process.env.PLAYCRAFT_PRODUCTION_CODEX_COMMAND,
|
|
11
|
-
homeDir: process.env.PLAYCRAFT_LAUNCHER_SOCKET
|
|
12
|
-
? `/var/lib/playcraft/codex/production-${input.task.attemptId}` : join(state.config.stateDir, 'production-native', input.task.attemptId),
|
|
13
|
-
model: input.model.model, processGroup: true,
|
|
14
|
-
fixedThreadConfig: { model: input.model.model, modelProvider: 'playcraft_production', config: {
|
|
15
|
-
approval_policy: 'never', sandbox_mode: 'danger-full-access',
|
|
16
|
-
'model_providers.playcraft_production': { name: 'PlayCraft Production', base_url: input.model.endpoint,
|
|
17
|
-
wire_api: 'responses', requires_openai_auth: false, http_headers: { Authorization: `Bearer ${input.model.token}` } },
|
|
18
|
-
} },
|
|
19
|
-
onSkillProcessRestart: async (process) => {
|
|
20
|
-
const group = { pid: process.pid, birth: await processBirth(process.pid), stopped: false };
|
|
21
|
-
groups.push(group);
|
|
22
|
-
await record(group);
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
track(() => adapter.close());
|
|
26
|
-
const sessionId = `production-${input.task.attemptId}`;
|
|
27
|
-
let completion;
|
|
28
|
-
const done = new Promise(resolve => { completion = resolve; });
|
|
29
|
-
const unsubscribe = adapter.onCollected?.(event => {
|
|
30
|
-
if (event.kind === 'process_exit')
|
|
31
|
-
completion('unknown');
|
|
32
|
-
if (event.method === 'turn/completed') {
|
|
33
|
-
const value = event.params;
|
|
34
|
-
completion(value.turn?.status === 'completed' ? 'completed' : 'failed');
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
try {
|
|
38
|
-
await adapter.start();
|
|
39
|
-
const process = adapter.executionProcess?.();
|
|
40
|
-
if (!process)
|
|
41
|
-
throw new Error('production_process_not_started');
|
|
42
|
-
const group = { pid: process.pid, birth: await processBirth(process.pid), stopped: false };
|
|
43
|
-
groups.push(group);
|
|
44
|
-
await record(group);
|
|
45
|
-
const thread = await adapter.startThread({ sessionId, cwd: root });
|
|
46
|
-
const turn = await adapter.startTurn({ sessionId, threadId: thread.threadId, text: String(input.payload.prompt ?? ''),
|
|
47
|
-
clientRequestId: `${input.task.attemptId}:generate`, persistReservation: async () => undefined });
|
|
48
|
-
if (turn.outcome !== 'accepted')
|
|
49
|
-
throw new Error('production_native_start_unknown');
|
|
50
|
-
const timeout = setTimeout(() => completion('unknown'), 30 * 60_000);
|
|
51
|
-
let checking = false;
|
|
52
|
-
const leaseCheck = setInterval(() => {
|
|
53
|
-
if (checking)
|
|
54
|
-
return;
|
|
55
|
-
checking = true;
|
|
56
|
-
void verifyProductionLease(input).catch(async () => { completion('unknown'); await adapter.close?.().catch(() => undefined); }).finally(() => { checking = false; });
|
|
57
|
-
}, 5000);
|
|
58
|
-
const status = await done;
|
|
59
|
-
clearInterval(leaseCheck);
|
|
60
|
-
clearTimeout(timeout);
|
|
61
|
-
if (status !== 'completed')
|
|
62
|
-
throw new Error(status === 'failed' ? 'production_native_failed' : 'production_native_unknown');
|
|
63
|
-
}
|
|
64
|
-
finally {
|
|
65
|
-
unsubscribe?.();
|
|
66
|
-
await adapter.close?.().catch(() => undefined);
|
|
67
|
-
for (const group of groups)
|
|
68
|
-
await record({ ...group, stopped: await stopProductionGroup(group) });
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { spawn, execFile } from 'node:child_process';
|
|
2
|
-
import { promisify } from 'node:util';
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
5
|
-
import { startLauncherProcess } from '../codex/launcher-client.js';
|
|
6
|
-
export async function processBirth(pid) {
|
|
7
|
-
if (process.platform !== 'linux') {
|
|
8
|
-
return promisify(execFile)('ps', ['-p', String(pid), '-o', 'lstart='], { timeout: 2000 }).then(value => value.stdout.trim() || undefined, () => undefined);
|
|
9
|
-
}
|
|
10
|
-
try {
|
|
11
|
-
const stat = await readFile(`/proc/${pid}/stat`, 'utf8');
|
|
12
|
-
return stat.slice(stat.lastIndexOf(')') + 2).split(' ')[19];
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
export function groupExited(pid) {
|
|
19
|
-
try {
|
|
20
|
-
process.kill(-pid, 0);
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
return error.code === 'ESRCH';
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
export async function stopProductionGroup(record) {
|
|
28
|
-
if (record.stopped || groupExited(record.pid))
|
|
29
|
-
return true;
|
|
30
|
-
if (record.birth && await processBirth(record.pid) !== record.birth)
|
|
31
|
-
return false;
|
|
32
|
-
for (const signal of ['SIGTERM', 'SIGKILL']) {
|
|
33
|
-
try {
|
|
34
|
-
process.kill(-record.pid, signal);
|
|
35
|
-
}
|
|
36
|
-
catch { /* Recheck whether the whole group exited. */ }
|
|
37
|
-
const deadline = Date.now() + 2000;
|
|
38
|
-
while (Date.now() < deadline) {
|
|
39
|
-
if (groupExited(record.pid))
|
|
40
|
-
return true;
|
|
41
|
-
await delay(25);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return groupExited(record.pid);
|
|
45
|
-
}
|
|
46
|
-
/** Only the save stage receives repository credentials; build and install receive none. */
|
|
47
|
-
export async function runProductionCommand(state, input) {
|
|
48
|
-
const launcher = !input.trusted ? process.env.PLAYCRAFT_LAUNCHER_SOCKET : undefined;
|
|
49
|
-
const launched = launcher ? await startLauncherProcess({ socketPath: launcher, processId: input.processId,
|
|
50
|
-
runtimeEpoch: state.runtimeEpoch, argv: ['/usr/bin/bash', '-lc', input.command], cwd: input.cwd, env: input.env ?? {} }) : undefined;
|
|
51
|
-
const local = launched ? undefined : spawn('bash', ['-lc', input.command], { cwd: input.cwd,
|
|
52
|
-
env: { PATH: process.env.PATH, HOME: state.config.stateDir, ...input.env }, detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
53
|
-
const pid = launched?.pid ?? local?.pid;
|
|
54
|
-
if (!pid)
|
|
55
|
-
throw new Error('production_process_not_started');
|
|
56
|
-
const record = { pid, birth: await processBirth(pid), stopped: false };
|
|
57
|
-
try {
|
|
58
|
-
await input.record(record);
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
await stopProductionGroup(record);
|
|
62
|
-
throw error;
|
|
63
|
-
}
|
|
64
|
-
let output = '';
|
|
65
|
-
const collect = (chunk) => { output = (output + String(chunk)).slice(-64 * 1024); };
|
|
66
|
-
(launched?.stdout ?? local.stdout).on('data', collect);
|
|
67
|
-
(launched?.stderr ?? local.stderr).on('data', collect);
|
|
68
|
-
launched?.stdin?.end();
|
|
69
|
-
let timedOut = false;
|
|
70
|
-
const code = await new Promise((resolve) => {
|
|
71
|
-
const timeout = setTimeout(() => {
|
|
72
|
-
timedOut = true;
|
|
73
|
-
void (launched ? launched.stop() : stopProductionGroup(record)).catch(() => undefined).finally(() => resolve(undefined));
|
|
74
|
-
}, input.timeoutMs);
|
|
75
|
-
const finish = (code) => { clearTimeout(timeout); resolve(code); };
|
|
76
|
-
if (launched)
|
|
77
|
-
launched.onExit(finish);
|
|
78
|
-
else {
|
|
79
|
-
local.once('error', () => finish(undefined));
|
|
80
|
-
local.once('close', finish);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
const stopped = await stopProductionGroup(record);
|
|
84
|
-
await input.record({ ...record, stopped });
|
|
85
|
-
return { code: timedOut ? undefined : code, output, stopped };
|
|
86
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export function productionSaveCommand() {
|
|
2
|
-
return `set -eu
|
|
3
|
-
status=0
|
|
4
|
-
remote=$(git -c protocol.version=0 -c core.hooksPath=/dev/null ls-remote --exit-code --heads "$PRODUCTION_REPOSITORY" "refs/heads/$PRODUCTION_BRANCH") || status=$?
|
|
5
|
-
if [ "$status" != 0 ] && [ "$status" != 2 ]; then exit "$status"; fi
|
|
6
|
-
if [ "\${PRODUCTION_EXPECTED_PARENT+x}" = x ]; then
|
|
7
|
-
actual=$(printf '%s' "$remote" | cut -f1)
|
|
8
|
-
test "$actual" = "$PRODUCTION_EXPECTED_PARENT" || { printf 'production_git_parent_changed\\n' >&2; exit 1; }
|
|
9
|
-
fi
|
|
10
|
-
if [ -n "$remote" ]; then
|
|
11
|
-
parent=$(printf '%s' "$remote" | cut -f1)
|
|
12
|
-
git -c protocol.version=0 -c core.hooksPath=/dev/null fetch --no-tags --no-recurse-submodules "$PRODUCTION_REPOSITORY" "refs/heads/$PRODUCTION_BRANCH"
|
|
13
|
-
test "$(git rev-parse FETCH_HEAD)" = "$parent"
|
|
14
|
-
git -c core.hooksPath=/dev/null -c core.fsmonitor=false reset --soft FETCH_HEAD
|
|
15
|
-
fi
|
|
16
|
-
git -c core.hooksPath=/dev/null -c core.fsmonitor=false add -A .
|
|
17
|
-
git diff --cached --quiet || git -c core.hooksPath=/dev/null commit -m "Save generated engineering"
|
|
18
|
-
git -c protocol.version=0 -c core.hooksPath=/dev/null push "$PRODUCTION_REPOSITORY" HEAD:"refs/heads/$PRODUCTION_BRANCH"
|
|
19
|
-
git rev-parse HEAD`;
|
|
20
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { lstat, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { dirname, join } from 'node:path';
|
|
5
|
-
import { promisify } from 'node:util';
|
|
6
|
-
import { batchBaselineSnapshotSchema } from '@playcraft/common/cloud-workspace/runtime';
|
|
7
|
-
import { checkoutBatchCommit, freezeBatchCommit } from '../workspaces/batch-commit.js';
|
|
8
|
-
import { openDirectoryToDevelopment } from '../workspaces/directory-access.js';
|
|
9
|
-
import { registerWorkspaceDirectory } from '../workspaces/directories.js';
|
|
10
|
-
import { workspaceRoot, workspaceSessions } from '../workspaces/registry.js';
|
|
11
|
-
import { excludeFromGit, settleDependencyInstalls } from '../workspaces/dependencies.js';
|
|
12
|
-
import { resolveProductionDependencies, verifyProductionDependencyLocks } from './dependencies.js';
|
|
13
|
-
import { productionSaveCommand } from './save-command.js';
|
|
14
|
-
import { productionOutputStore, verifyProductionLease } from './storage.js';
|
|
15
|
-
import { runProductionCommand } from './processes.js';
|
|
16
|
-
import { runProductionNative } from './native.js';
|
|
17
|
-
import { classifyPreviewOutput, findExternalLocalReferences } from '../previews/single-file.js';
|
|
18
|
-
const execute = promisify(execFile);
|
|
19
|
-
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
20
|
-
const hash = (body) => createHash('sha256').update(body).digest('hex');
|
|
21
|
-
const record = (value) => value && typeof value === 'object' ? value : {};
|
|
22
|
-
export async function executeProductionStage(state, input, processRecord, track) {
|
|
23
|
-
await verifyProductionLease(input);
|
|
24
|
-
const store = productionOutputStore(input);
|
|
25
|
-
const stage = input.stage;
|
|
26
|
-
const workspaceId = input.task.workspaceId;
|
|
27
|
-
const resultId = String(input.payload.resultVersionId ?? input.task.payload.resultVersionId ?? input.task.attemptId);
|
|
28
|
-
const previous = (name) => input.stages?.find(value => value.stage === name && value.status === 'completed')?.result;
|
|
29
|
-
const run = async (cwd, command, env, trusted = false) => {
|
|
30
|
-
const outcome = await runProductionCommand(state, { processId: `production-${input.task.attemptId}-${stage}`, cwd, command, env, trusted,
|
|
31
|
-
timeoutMs: 10 * 60_000, record: processRecord });
|
|
32
|
-
if (!outcome.stopped || outcome.code === undefined)
|
|
33
|
-
throw new Error('production_process_unknown');
|
|
34
|
-
if (outcome.code !== 0)
|
|
35
|
-
throw new Error(`production_${stage}_failed`);
|
|
36
|
-
return outcome.output;
|
|
37
|
-
};
|
|
38
|
-
const git = async (root, ...args) => (await execute('git', ['-c', 'core.hooksPath=/dev/null', '-c', 'core.fsmonitor=false', '-C', root, ...args], {
|
|
39
|
-
timeout: 20_000, maxBuffer: 1024 * 1024, env: { PATH: process.env.PATH, HOME: state.config.stateDir, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_NOSYSTEM: '1' },
|
|
40
|
-
})).stdout.trim();
|
|
41
|
-
if (stage === 'prepare' && input.directoryName !== 'basic') {
|
|
42
|
-
const baseline = record(input.payload.baseline ?? input.task.payload.baseline);
|
|
43
|
-
const authority = record(input.payload.workspace);
|
|
44
|
-
const sourceCommit = baseline.commitHash;
|
|
45
|
-
if (typeof sourceCommit !== 'string')
|
|
46
|
-
throw new Error('production_approved_commit_required');
|
|
47
|
-
const directory = { workspaceId, directoryName: input.directoryName,
|
|
48
|
-
remixId: String(authority.remixId), branchName: String(authority.branchName), sourceCommit };
|
|
49
|
-
await registerWorkspaceDirectory(state, directory, state.acceptedLeaseFence);
|
|
50
|
-
}
|
|
51
|
-
const root = await workspaceRoot(state, workspaceId, input.directoryName);
|
|
52
|
-
if (stage === 'capture-source') {
|
|
53
|
-
if (input.directoryName !== 'basic')
|
|
54
|
-
throw new Error('production_source_directory_mismatch');
|
|
55
|
-
if (workspaceSessions(state, workspaceId, 'basic').some(session => session.turnBlock === 'uncertain' || ['starting', 'running', 'waiting_for_user'].includes(session.turnStatus ?? '')))
|
|
56
|
-
throw new Error('production_source_busy');
|
|
57
|
-
await settleDependencyInstalls(state);
|
|
58
|
-
await resolveProductionDependencies(root);
|
|
59
|
-
await excludeFromGit(root);
|
|
60
|
-
await excludeFromGit(root, '/.env*');
|
|
61
|
-
await git(root, 'config', 'user.name', 'PlayCraft');
|
|
62
|
-
await git(root, 'config', 'user.email', 'playcraft@localhost');
|
|
63
|
-
await run(root, 'git -c core.hooksPath=/dev/null add -A . && (git diff --cached --quiet || git -c core.hooksPath=/dev/null commit -m "Preserve basic before common repair")', undefined, true);
|
|
64
|
-
const commitHash = await git(root, 'rev-parse', 'HEAD');
|
|
65
|
-
const snapshot = await freezeBatchCommit({ source: { workspaceId, root }, commitHash, snapshotId: `${input.task.attemptId}-capture-source` });
|
|
66
|
-
return { sourceSnapshot: snapshot, commitHash };
|
|
67
|
-
}
|
|
68
|
-
if (stage === 'prepare') {
|
|
69
|
-
await settleDependencyInstalls(state);
|
|
70
|
-
const policy = await resolveProductionDependencies(root);
|
|
71
|
-
await run(root, process.env.PLAYCRAFT_PRODUCTION_INSTALL_COMMAND ?? 'pnpm install --frozen-lockfile --store-dir /var/cache/playcraft/pnpm-store --ignore-scripts');
|
|
72
|
-
await verifyProductionDependencyLocks(root, policy);
|
|
73
|
-
return { prepared: true, directoryName: input.directoryName };
|
|
74
|
-
}
|
|
75
|
-
if (stage === 'generate') {
|
|
76
|
-
await runProductionNative(state, input, root, processRecord, track);
|
|
77
|
-
await resolveProductionDependencies(root);
|
|
78
|
-
return { generated: true };
|
|
79
|
-
}
|
|
80
|
-
if (stage === 'save') {
|
|
81
|
-
if (!input.git)
|
|
82
|
-
throw new Error('production_git_scope_required');
|
|
83
|
-
const binding = state.journal.getWorkspace(workspaceId);
|
|
84
|
-
const branch = input.directoryName === 'basic' ? binding.branchName : binding.directories?.find(value => value.directoryName === input.directoryName)?.branchName;
|
|
85
|
-
if (input.git.branchName !== branch || await git(root, 'symbolic-ref', '--short', 'HEAD') !== branch)
|
|
86
|
-
throw new Error('production_git_binding_mismatch');
|
|
87
|
-
await excludeFromGit(root);
|
|
88
|
-
await excludeFromGit(root, '/.env*');
|
|
89
|
-
await excludeFromGit(root, '/.playcraft-temp/');
|
|
90
|
-
await excludeFromGit(root, '/.playcraft-preview-out/');
|
|
91
|
-
await git(root, 'config', 'user.name', 'PlayCraft');
|
|
92
|
-
await git(root, 'config', 'user.email', 'playcraft@localhost');
|
|
93
|
-
const env = { PRODUCTION_REPOSITORY: input.git.repositoryUrl, PRODUCTION_BRANCH: input.git.branchName,
|
|
94
|
-
GIT_TERMINAL_PROMPT: '0', GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_NOSYSTEM: '1' };
|
|
95
|
-
if (input.git.username && input.git.password)
|
|
96
|
-
Object.assign(env, { GIT_CONFIG_COUNT: '1',
|
|
97
|
-
GIT_CONFIG_KEY_0: `http.${input.git.repositoryUrl}.extraHeader`,
|
|
98
|
-
GIT_CONFIG_VALUE_0: `Authorization: Basic ${Buffer.from(`${input.git.username}:${input.git.password}`).toString('base64')}` });
|
|
99
|
-
if (typeof input.payload.expectedParent === 'string')
|
|
100
|
-
env.PRODUCTION_EXPECTED_PARENT = input.payload.expectedParent;
|
|
101
|
-
await run(root, productionSaveCommand(), env, true);
|
|
102
|
-
const commitHash = await git(root, 'rev-parse', 'HEAD');
|
|
103
|
-
const snapshot = await freezeBatchCommit({ source: { workspaceId, root }, commitHash, snapshotId: `${input.task.attemptId}-save` });
|
|
104
|
-
return { snapshot, saved: { attemptId: input.task.attemptId, resultVersionId: resultId, workspaceId, sourceDigest: snapshot.checksum, commitHash, pushedCommitHash: commitHash } };
|
|
105
|
-
}
|
|
106
|
-
if (stage === 'freeze') {
|
|
107
|
-
const saved = record(previous('save')?.saved);
|
|
108
|
-
if (typeof saved.commitHash !== 'string')
|
|
109
|
-
throw new Error('production_saved_commit_required');
|
|
110
|
-
const snapshot = await freezeBatchCommit({ source: { workspaceId, root }, commitHash: saved.commitHash, snapshotId: `${input.task.attemptId}-freeze` });
|
|
111
|
-
const baseline = batchBaselineSnapshotSchema.parse({ batchId: input.task.batchId, projectId: String(input.payload.projectId ?? input.task.payload.projectId),
|
|
112
|
-
snapshotId: snapshot.snapshotId, sourceWorkspaceId: snapshot.sourceWorkspaceId, commitHash: snapshot.commitHash, checksum: snapshot.checksum,
|
|
113
|
-
sourceVersion: snapshot.sourceVersion, engine: String(input.payload.engine ?? input.task.payload.engine), skillLockDigest: snapshot.skillLockDigest, frozenAt: snapshot.frozenAt });
|
|
114
|
-
return { snapshot, baseline };
|
|
115
|
-
}
|
|
116
|
-
if (stage === 'build') {
|
|
117
|
-
const saved = record(input.payload.saved ?? previous('save')?.saved);
|
|
118
|
-
const snapshot = (previous('freeze')?.snapshot ?? previous('save')?.snapshot);
|
|
119
|
-
if (!snapshot || snapshot.commitHash !== saved.commitHash || snapshot.checksum !== saved.sourceDigest)
|
|
120
|
-
throw new Error('production_saved_snapshot_mismatch');
|
|
121
|
-
const destination = join(root, '.playcraft-temp', `production-${input.task.attemptId}`);
|
|
122
|
-
await excludeFromGit(root, '/.playcraft-temp/');
|
|
123
|
-
await rm(destination, { recursive: true, force: true });
|
|
124
|
-
try {
|
|
125
|
-
await checkoutBatchCommit({ source: { workspaceId, root }, frozen: snapshot, destination });
|
|
126
|
-
await openDirectoryToDevelopment(destination, (await lstat(state.config.workspaceRoot)).gid);
|
|
127
|
-
const policy = await resolveProductionDependencies(destination);
|
|
128
|
-
await run(destination, process.env.PLAYCRAFT_PRODUCTION_INSTALL_COMMAND ?? 'pnpm install --frozen-lockfile --store-dir /var/cache/playcraft/pnpm-store --ignore-scripts');
|
|
129
|
-
await verifyProductionDependencyLocks(destination, policy);
|
|
130
|
-
const output = join(destination, '.production-html');
|
|
131
|
-
const command = process.env.PLAYCRAFT_PRODUCTION_BUILD_COMMAND ?? 'playcraft build --mode full --platform playcraft --format html';
|
|
132
|
-
await run(destination, `${command} --output ${quote(output)} .`);
|
|
133
|
-
await verifyProductionDependencyLocks(destination, policy);
|
|
134
|
-
const entries = await readdir(output, { withFileTypes: true });
|
|
135
|
-
const single = classifyPreviewOutput(entries.map(entry => ({ name: entry.name, kind: entry.isFile() ? 'file' : entry.isDirectory() ? 'dir' : 'symlink' })));
|
|
136
|
-
if (!single.ok)
|
|
137
|
-
throw new Error('production_preview_not_single_file');
|
|
138
|
-
const path = join(output, single.entry);
|
|
139
|
-
if (!(await lstat(path)).isFile())
|
|
140
|
-
throw new Error('production_preview_invalid');
|
|
141
|
-
const body = await readFile(path);
|
|
142
|
-
if (findExternalLocalReferences(body.toString()).length)
|
|
143
|
-
throw new Error('production_preview_external_reference');
|
|
144
|
-
const outputDigest = hash(body), outputKey = `workspaces/${workspaceId}/production/${input.task.attemptId}/${outputDigest}.html`;
|
|
145
|
-
await store.put(outputKey, body);
|
|
146
|
-
return { outputKey, outputDigest, build: { attemptId: input.task.attemptId, resultVersionId: resultId, workspaceId,
|
|
147
|
-
sourceDigest: snapshot.checksum, commitHash: snapshot.commitHash, buildId: `${input.task.attemptId}-build`, succeeded: true } };
|
|
148
|
-
}
|
|
149
|
-
finally {
|
|
150
|
-
await rm(destination, { recursive: true, force: true });
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (stage === 'preview') {
|
|
154
|
-
const built = previous('build');
|
|
155
|
-
if (typeof built?.outputKey !== 'string' || typeof built.outputDigest !== 'string')
|
|
156
|
-
throw new Error('production_build_required');
|
|
157
|
-
const body = await store.get(built.outputKey);
|
|
158
|
-
if (hash(body) !== built.outputDigest)
|
|
159
|
-
throw new Error('production_preview_digest_mismatch');
|
|
160
|
-
const previewPath = join(root, '.playcraft-temp', `${input.task.attemptId}.html`);
|
|
161
|
-
await mkdir(dirname(previewPath), { recursive: true });
|
|
162
|
-
await writeFile(previewPath, body);
|
|
163
|
-
try {
|
|
164
|
-
const browser = process.env.PLAYCRAFT_PRODUCTION_PREVIEW_COMMAND ?? 'chromium --headless --no-sandbox --disable-gpu --virtual-time-budget=1000 --dump-dom';
|
|
165
|
-
const opened = await run(root, `${browser} ${quote(`file://${previewPath}`)}`);
|
|
166
|
-
if (!/<html[\s>]/i.test(opened))
|
|
167
|
-
throw new Error('production_preview_not_opened');
|
|
168
|
-
return { opened: true, outputDigest: built.outputDigest };
|
|
169
|
-
}
|
|
170
|
-
finally {
|
|
171
|
-
await rm(previewPath, { force: true });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
throw new Error('invalid_production_stage');
|
|
175
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const taskUrl = (input) => `${input.callbackUrl.replace(/\/+$/, '')}/internal/cloud-production/tasks/${input.task.taskId}`;
|
|
2
|
-
/** Build output travels through the Runtime that leased the task; engineering files travel through Git. */
|
|
3
|
-
export function productionOutputStore(input) {
|
|
4
|
-
const object = (key) => `${taskUrl(input)}/objects/${encodeURIComponent(key)}`;
|
|
5
|
-
const headers = { authorization: `Bearer ${input.runnerToken}`, 'x-playcraft-object-client': 'cli' };
|
|
6
|
-
return {
|
|
7
|
-
async get(key) {
|
|
8
|
-
const response = await fetch(object(key), { headers, redirect: 'error', signal: AbortSignal.timeout(60_000) });
|
|
9
|
-
if (response.status === 404)
|
|
10
|
-
throw new Error('object_missing');
|
|
11
|
-
if (!response.ok)
|
|
12
|
-
throw new Error('object_get_failed');
|
|
13
|
-
return Buffer.from(await response.arrayBuffer());
|
|
14
|
-
},
|
|
15
|
-
async put(key, body) {
|
|
16
|
-
const response = await fetch(object(key), { method: 'PUT', body: new Uint8Array(body), redirect: 'error', signal: AbortSignal.timeout(60_000),
|
|
17
|
-
headers: { ...headers, 'content-type': 'application/octet-stream' } });
|
|
18
|
-
if (!response.ok)
|
|
19
|
-
throw new Error(response.status === 409 ? 'object_conflict' : 'object_put_failed');
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
export async function verifyProductionLease(input) {
|
|
24
|
-
const response = await fetch(`${taskUrl(input)}/runner`, {
|
|
25
|
-
headers: { authorization: `Bearer ${input.runnerToken}` }, redirect: 'error', signal: AbortSignal.timeout(10_000),
|
|
26
|
-
});
|
|
27
|
-
if (!response.ok)
|
|
28
|
-
throw new Error('production_lease_unavailable');
|
|
29
|
-
const { task } = await response.json();
|
|
30
|
-
if (!task || task.taskId !== input.task.taskId || task.attemptId !== input.task.attemptId || task.leaseOwner !== input.lease.owner || task.leaseFence !== input.lease.fence || task.status !== 'running' || task.cancelRequested)
|
|
31
|
-
throw new Error('production_lease_unavailable');
|
|
32
|
-
}
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import { closeCliCredentials } from './workspaces/cli-credentials.js';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { mkdir } from 'node:fs/promises';
|
|
4
|
-
import { createServer } from 'node:http';
|
|
5
|
-
import { INCOMPATIBLE_EXIT_CODE, negotiateProtocol, } from '@playcraft/common/cloud-workspace/runtime';
|
|
6
|
-
import { RuntimeJournal } from './persistence/journal.js';
|
|
7
|
-
import { createCodexAppServerAdapter } from './codex/app-server.js';
|
|
8
|
-
import { createLoopbackNativeAdapter } from './codex/loopback.js';
|
|
9
|
-
import { attachNativeCollector } from './server/dispatch.js';
|
|
10
|
-
import { handleRuntimeRequest } from './server/http.js';
|
|
11
|
-
import { desktopHost, disposeDesktopHost } from './server/desktop-host.js';
|
|
12
|
-
import { disposeTerminalHost, terminalHost } from './server/terminal-host.js';
|
|
13
|
-
import { handleExecutionGroupCrash } from './server/execution-group.js';
|
|
14
|
-
import { RuntimeProcessLock } from './server/lock.js';
|
|
15
|
-
import { probeGitBinary } from './workspaces/git.js';
|
|
16
|
-
import { WorkspaceGitCredentialBroker } from './workspaces/git-credential-broker.js';
|
|
17
|
-
import { WorkspaceModelBroker } from './codex/model-broker.js';
|
|
18
|
-
import { recoverInterruptedPreviews } from './previews/publish.js';
|
|
19
|
-
import { recoverInterruptedInstalls } from './workspaces/dependencies.js';
|
|
20
|
-
import { loadWorkspaceSkillArtifact } from './workspaces/skill-artifact-loader.js';
|
|
21
|
-
import { workspaceContextFromRoot } from './workspaces/registry.js';
|
|
22
|
-
export async function startWorkspaceRuntime(config, options = {}) {
|
|
23
|
-
process.env.PLAYCRAFT_SKIP_UPDATE_CHECK = '1';
|
|
24
|
-
const negotiated = negotiateProtocol(config);
|
|
25
|
-
if (!negotiated.ok) {
|
|
26
|
-
const error = Object.assign(new Error('protocol_incompatible'), {
|
|
27
|
-
code: 'protocol_incompatible',
|
|
28
|
-
exitCode: INCOMPATIBLE_EXIT_CODE,
|
|
29
|
-
});
|
|
30
|
-
throw error;
|
|
31
|
-
}
|
|
32
|
-
const lock = new RuntimeProcessLock(config.stateDir);
|
|
33
|
-
lock.acquire();
|
|
34
|
-
let journal;
|
|
35
|
-
let gitCredentials;
|
|
36
|
-
try {
|
|
37
|
-
journal = new RuntimeJournal(config.stateDir);
|
|
38
|
-
await journal.load();
|
|
39
|
-
const previousEpoch = journal.storedRuntimeEpoch();
|
|
40
|
-
const previousGroup = journal.executionGroup();
|
|
41
|
-
const previousIdentityMatches = journal.epochsSnapshot().some((epoch) => epoch.runtime_id === config.runtimeId && epoch.generation === config.generation && epoch.runtime_epoch === previousEpoch);
|
|
42
|
-
await recoverInterruptedInstalls(journal);
|
|
43
|
-
await recoverInterruptedPreviews(journal);
|
|
44
|
-
await mkdir(config.workspaceRoot, { recursive: true, mode: 0o700 });
|
|
45
|
-
const state = {
|
|
46
|
-
config: { ...config, protocolMinor: negotiated.protocolMinor },
|
|
47
|
-
configPath: options.configPath,
|
|
48
|
-
journal,
|
|
49
|
-
acceptedLeaseFence: journal.acceptedLeaseFence(),
|
|
50
|
-
gitAvailable: await probeGitBinary(),
|
|
51
|
-
native: options.nativeAdapter,
|
|
52
|
-
runtimeEpoch: Math.max(config.runtimeEpoch, journal.storedRuntimeEpoch() ?? 0),
|
|
53
|
-
executionQueue: Promise.resolve(),
|
|
54
|
-
instanceToken: randomUUID(),
|
|
55
|
-
};
|
|
56
|
-
const native = state.native ??= defaultNativeAdapter(state);
|
|
57
|
-
await journal.bindRuntime({
|
|
58
|
-
runtimeId: config.runtimeId,
|
|
59
|
-
generation: config.generation,
|
|
60
|
-
runtimeEpoch: state.runtimeEpoch,
|
|
61
|
-
});
|
|
62
|
-
if (config.workspaceMode === 'multi') {
|
|
63
|
-
gitCredentials = state.gitCredentials = new WorkspaceGitCredentialBroker(state);
|
|
64
|
-
await gitCredentials.start();
|
|
65
|
-
}
|
|
66
|
-
attachNativeCollector(state);
|
|
67
|
-
native?.onUnexpectedExit?.((event) => {
|
|
68
|
-
void handleExecutionGroupCrash(state, event.pid);
|
|
69
|
-
});
|
|
70
|
-
try {
|
|
71
|
-
await native?.start?.();
|
|
72
|
-
const group = native?.executionProcess?.();
|
|
73
|
-
if (group) {
|
|
74
|
-
let previousStopped = false;
|
|
75
|
-
if (previousGroup.pid && previousGroup.pid !== group.pid) {
|
|
76
|
-
try {
|
|
77
|
-
process.kill(previousGroup.pid, 0);
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
previousStopped = error.code === 'ESRCH';
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// The supervisor advances the trusted config epoch only after stopping the old
|
|
84
|
-
// native group. Reconfirm its recorded PID before publishing that replacement.
|
|
85
|
-
if (previousIdentityMatches && previousEpoch !== undefined && config.runtimeEpoch > previousEpoch && previousStopped) {
|
|
86
|
-
await journal.writeExecutionEpoch({
|
|
87
|
-
runtimeEpoch: state.runtimeEpoch, ...group, previousPid: previousGroup.pid, stopConfirmed: true,
|
|
88
|
-
});
|
|
89
|
-
await journal.replaceSessions(journal.listSessions().map((session) => ({ ...session, recoverability: 'history_only' })));
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
await journal.noteExecutionGroup(group);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
const code = error instanceof Error ? error.message : '';
|
|
98
|
-
state.nativeError = code === 'codex_model_catalog_unavailable' ? code : 'codex_start_failed';
|
|
99
|
-
console.error(`[workspace-runtime] ${state.nativeError}`);
|
|
100
|
-
}
|
|
101
|
-
const server = createServer((request, response) => {
|
|
102
|
-
void handleRuntimeRequest(state, request, response);
|
|
103
|
-
});
|
|
104
|
-
server.on('upgrade', (request, socket, head) => {
|
|
105
|
-
const pathname = new URL(request.url ?? '/', 'http://127.0.0.1').pathname;
|
|
106
|
-
if (/^\/runtime\/terminals\/[^/]+\/connect$/.test(pathname)) {
|
|
107
|
-
void terminalHost(state).handleUpgrade(request, socket, head);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (pathname === '/runtime/desktop/connect') {
|
|
111
|
-
desktopHost(state).handleUpgrade(request, socket, head);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
socket.destroy();
|
|
115
|
-
});
|
|
116
|
-
const port = await listenOn(server, config.listen.host, config.listen.port);
|
|
117
|
-
return {
|
|
118
|
-
config: state.config,
|
|
119
|
-
state,
|
|
120
|
-
url: `http://${formatHost(config.listen.host)}:${port}`,
|
|
121
|
-
async close() {
|
|
122
|
-
const serverClosed = closeServer(server);
|
|
123
|
-
await closeCliCredentials(state);
|
|
124
|
-
await gitCredentials?.close();
|
|
125
|
-
try {
|
|
126
|
-
await disposeDesktopHost(state);
|
|
127
|
-
}
|
|
128
|
-
finally {
|
|
129
|
-
disposeTerminalHost(state);
|
|
130
|
-
try {
|
|
131
|
-
await native?.close?.();
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
/* adapter shutdown is best-effort */
|
|
135
|
-
}
|
|
136
|
-
await serverClosed;
|
|
137
|
-
await journal?.close();
|
|
138
|
-
lock.release();
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
await gitCredentials?.close().catch(() => undefined);
|
|
145
|
-
await journal?.close().catch(() => undefined);
|
|
146
|
-
lock.release();
|
|
147
|
-
throw error;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
function listenOn(server, host, port) {
|
|
151
|
-
return new Promise((resolve, reject) => {
|
|
152
|
-
const onError = (error) => reject(error);
|
|
153
|
-
server.once('error', onError);
|
|
154
|
-
server.listen(port, host, () => {
|
|
155
|
-
server.removeListener('error', onError);
|
|
156
|
-
const address = server.address();
|
|
157
|
-
if (!address || typeof address === 'string') {
|
|
158
|
-
reject(new Error('listen_failed'));
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
resolve(address.port);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
function closeServer(server) {
|
|
166
|
-
return new Promise((resolve, reject) => {
|
|
167
|
-
server.close((error) => {
|
|
168
|
-
if (error)
|
|
169
|
-
reject(error);
|
|
170
|
-
else
|
|
171
|
-
resolve();
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
function formatHost(host) {
|
|
176
|
-
return host.includes(':') ? `[${host}]` : host;
|
|
177
|
-
}
|
|
178
|
-
function defaultNativeAdapter(state) {
|
|
179
|
-
const config = state.config;
|
|
180
|
-
if (process.env.PLAYCRAFT_NATIVE_ADAPTER === 'loopback')
|
|
181
|
-
return createLoopbackNativeAdapter();
|
|
182
|
-
const hasLauncher = Boolean(process.env.PLAYCRAFT_LAUNCHER_SOCKET);
|
|
183
|
-
const multi = config.workspaceMode === 'multi';
|
|
184
|
-
if (process.env.PLAYCRAFT_NATIVE_ADAPTER === 'codex' && !multi && !hasLauncher) {
|
|
185
|
-
throw new Error('codex_requires_managed_launcher');
|
|
186
|
-
}
|
|
187
|
-
if (process.env.PLAYCRAFT_NATIVE_ADAPTER === 'codex' || (multi && hasLauncher)) {
|
|
188
|
-
return createCodexAppServerAdapter({
|
|
189
|
-
config,
|
|
190
|
-
...(multi ? { modelBroker: new WorkspaceModelBroker(state) } : {}),
|
|
191
|
-
loadSkillArtifact: async (cwd, entry) => {
|
|
192
|
-
const { workspaceId, directoryName } = await workspaceContextFromRoot(state, cwd);
|
|
193
|
-
return loadWorkspaceSkillArtifact(state, workspaceId, entry, directoryName === 'root' ? 'basic' : directoryName);
|
|
194
|
-
},
|
|
195
|
-
onSkillProcessRestart: process => state.journal.noteExecutionGroup(process),
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
return undefined;
|
|
199
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
-
export function extractControlToken(headers) {
|
|
3
|
-
const authorization = headerValue(headers.authorization);
|
|
4
|
-
if (authorization?.startsWith('Bearer ')) {
|
|
5
|
-
return authorization.slice('Bearer '.length).trim() || null;
|
|
6
|
-
}
|
|
7
|
-
const dedicated = headerValue(headers['x-playcraft-control-token']);
|
|
8
|
-
return dedicated?.trim() || null;
|
|
9
|
-
}
|
|
10
|
-
export function controlTokenMatches(expected, actual) {
|
|
11
|
-
if (!actual)
|
|
12
|
-
return false;
|
|
13
|
-
const left = Buffer.from(expected);
|
|
14
|
-
const right = Buffer.from(actual);
|
|
15
|
-
if (left.length !== right.length)
|
|
16
|
-
return false;
|
|
17
|
-
return timingSafeEqual(left, right);
|
|
18
|
-
}
|
|
19
|
-
function headerValue(value) {
|
|
20
|
-
return Array.isArray(value) ? value[0] : value;
|
|
21
|
-
}
|