@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,574 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
-
import { startLauncherProcess } from './launcher-client.js';
|
|
4
|
-
import { chmod, mkdtemp, rm } from 'node:fs/promises';
|
|
5
|
-
import { relative, join } from 'node:path';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { portableCodexConfig, writePortableCodexCatalog } from './config-toml.js';
|
|
8
|
-
import { createJsonRpcStdioClient } from './jsonrpc-stdio.js';
|
|
9
|
-
import { projectNativeThread } from '@playcraft/common/cloud-workspace/runtime';
|
|
10
|
-
import { collectedEvent, NativePreparationError } from './native-adapter.js';
|
|
11
|
-
import { readProjectSkillsLockFile } from '@playcraft/common/project-skills';
|
|
12
|
-
import { freezeWorkspaceSkills } from '../workspaces/skills.js';
|
|
13
|
-
function asRecord(value) {
|
|
14
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
15
|
-
? value
|
|
16
|
-
: {};
|
|
17
|
-
}
|
|
18
|
-
function waitForChildExit(child) {
|
|
19
|
-
if (child.exitCode !== null || child.signalCode !== null)
|
|
20
|
-
return Promise.resolve();
|
|
21
|
-
return new Promise((resolve) => {
|
|
22
|
-
const done = () => {
|
|
23
|
-
clearTimeout(timer);
|
|
24
|
-
resolve();
|
|
25
|
-
};
|
|
26
|
-
const timer = setTimeout(() => {
|
|
27
|
-
if (child.exitCode === null && child.signalCode === null)
|
|
28
|
-
child.kill('SIGKILL');
|
|
29
|
-
}, 3_000);
|
|
30
|
-
child.once('exit', done);
|
|
31
|
-
if (child.exitCode !== null || child.signalCode !== null)
|
|
32
|
-
done();
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function spawnCommand(bin) {
|
|
36
|
-
if (bin.endsWith('.mjs') || bin.endsWith('.js') || bin.endsWith('.ts')) {
|
|
37
|
-
return { command: process.execPath, prefix: [bin] };
|
|
38
|
-
}
|
|
39
|
-
return { command: bin, prefix: [] };
|
|
40
|
-
}
|
|
41
|
-
export function createCodexAppServerAdapter(options) {
|
|
42
|
-
const launcherSocket = options.launcherSocket || process.env.PLAYCRAFT_LAUNCHER_SOCKET;
|
|
43
|
-
const homeDir = options.homeDir || process.env.PLAYCRAFT_CODEX_HOME ||
|
|
44
|
-
(launcherSocket ? '/var/lib/playcraft/codex' : join(options.config.stateDir, 'codex-home'));
|
|
45
|
-
let executionEpoch = options.config.runtimeEpoch;
|
|
46
|
-
const model = options.model || process.env.PLAYCRAFT_CODEX_MODEL || 'gpt-4o';
|
|
47
|
-
const turnTimeoutMs = options.turnTimeoutMs ?? Number(process.env.PLAYCRAFT_CODEX_TURN_TIMEOUT_MS || 120_000);
|
|
48
|
-
const threads = new Map();
|
|
49
|
-
const approvals = new Map();
|
|
50
|
-
let child;
|
|
51
|
-
let rpc;
|
|
52
|
-
let readyFlag = false;
|
|
53
|
-
let closed = false;
|
|
54
|
-
let startPromise;
|
|
55
|
-
let catalogDirectory;
|
|
56
|
-
let skillsRestartFailed = false;
|
|
57
|
-
let preparationQueue = Promise.resolve();
|
|
58
|
-
let live;
|
|
59
|
-
const exitListeners = new Set();
|
|
60
|
-
const collected = new Set();
|
|
61
|
-
function emitCollected(event) {
|
|
62
|
-
for (const listener of collected)
|
|
63
|
-
listener(event);
|
|
64
|
-
}
|
|
65
|
-
async function withPreparation(action) {
|
|
66
|
-
const previous = preparationQueue;
|
|
67
|
-
let release;
|
|
68
|
-
preparationQueue = new Promise(resolve => { release = resolve; });
|
|
69
|
-
await previous;
|
|
70
|
-
try {
|
|
71
|
-
return await action();
|
|
72
|
-
}
|
|
73
|
-
finally {
|
|
74
|
-
release();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
async function ensureStarted() {
|
|
78
|
-
if (closed)
|
|
79
|
-
throw new Error('codex_not_started');
|
|
80
|
-
if (skillsRestartFailed)
|
|
81
|
-
throw new NativePreparationError('workspace_skills_unavailable');
|
|
82
|
-
if (readyFlag && rpc)
|
|
83
|
-
return;
|
|
84
|
-
startPromise ??= start();
|
|
85
|
-
await startPromise;
|
|
86
|
-
}
|
|
87
|
-
async function threadConfig(cwd, modelConfigId, sessionId) {
|
|
88
|
-
try {
|
|
89
|
-
return await options.modelBroker?.prepareThreadConfig(cwd, modelConfigId, sessionId)
|
|
90
|
-
?? { ...options.fixedThreadConfig, modelConfigId: undefined, activate: () => { } };
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
const reason = error instanceof Error ? error.message : '';
|
|
94
|
-
throw new NativePreparationError(['model_config_required',
|
|
95
|
-
'model_credential_scope_mismatch', 'model_config_denied', 'workspace_not_registered'].includes(reason)
|
|
96
|
-
? reason : 'model_credential_unavailable');
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
async function lockedThreadConfig(cwd, selectedConfigId, sessionId) {
|
|
100
|
-
const { modelConfigId, activate, ...modelConfig } = await threadConfig(cwd, selectedConfigId, sessionId);
|
|
101
|
-
// Single-workspace legacy runners retain their prior behaviour until explicitly adopted.
|
|
102
|
-
if (await readProjectSkillsLockFile(cwd) === null) {
|
|
103
|
-
const scope = relative(options.config.workspaceRoot, cwd);
|
|
104
|
-
const orchestration = /^[0-9a-f-]{36}$/i.test(scope);
|
|
105
|
-
if (options.config.workspaceMode === 'multi' && !orchestration)
|
|
106
|
-
throw new NativePreparationError('workspace_skills_unavailable');
|
|
107
|
-
return { params: modelConfig, modelConfigId, activate, skillsHash: undefined, discoveryHash: undefined };
|
|
108
|
-
}
|
|
109
|
-
try {
|
|
110
|
-
const frozen = await freezeWorkspaceSkills(cwd, join(options.config.stateDir, 'skill-artifacts'), options.loadSkillArtifact ? entry => options.loadSkillArtifact(cwd, entry) : undefined);
|
|
111
|
-
if (!rpc)
|
|
112
|
-
throw new Error('codex_not_started');
|
|
113
|
-
const found = await rpc.request('skills/list', { cwds: [cwd], forceReload: true }, 30_000);
|
|
114
|
-
const listing = found.data.find(item => item.cwd === cwd);
|
|
115
|
-
if (!listing || listing.errors.length)
|
|
116
|
-
throw new Error('workspace_skills_unavailable');
|
|
117
|
-
// Native scanning is used only to disable paths. Availability comes exclusively from the lock.
|
|
118
|
-
const config = { ...('config' in modelConfig ? modelConfig.config : {}),
|
|
119
|
-
'skills.include_instructions': false,
|
|
120
|
-
'skills.config': listing.skills.map(skill => ({ path: skill.path, enabled: false })) };
|
|
121
|
-
const developerInstructions = [
|
|
122
|
-
`Available workspace skills are fixed by lock ${frozen.declarationHash}.`,
|
|
123
|
-
'Use only the following locked skills. Read its SKILL.md when a skill applies, and resolve supporting files relative to that file. Disabled and unlisted skill directories are not available.',
|
|
124
|
-
...frozen.skills.map(skill => JSON.stringify(skill)),
|
|
125
|
-
...(frozen.skills.length ? [] : ['(No workspace skills are enabled.)']),
|
|
126
|
-
].join('\n');
|
|
127
|
-
const discoveryHash = createHash('sha256').update(JSON.stringify(listing.skills.map(skill => skill.path).sort())).digest('hex');
|
|
128
|
-
return { params: { ...modelConfig, config, developerInstructions }, modelConfigId, activate, skillsHash: frozen.lockHash, discoveryHash };
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
throw new NativePreparationError('workspace_skills_unavailable');
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
async function restartForSkills(threadId, selectedConfigId) {
|
|
135
|
-
if ([...threads.values()].some(thread => thread.turnActive) || approvals.size)
|
|
136
|
-
throw new NativePreparationError('skills_reload_pending');
|
|
137
|
-
const previous = live;
|
|
138
|
-
if (previous)
|
|
139
|
-
previous.superseded = true;
|
|
140
|
-
readyFlag = false;
|
|
141
|
-
await previous?.rpc.close();
|
|
142
|
-
await previous?.child.stop();
|
|
143
|
-
if (closed)
|
|
144
|
-
throw new Error('codex_not_started');
|
|
145
|
-
rpc = undefined;
|
|
146
|
-
startPromise = undefined;
|
|
147
|
-
for (const thread of threads.values()) {
|
|
148
|
-
thread.loaded = false;
|
|
149
|
-
thread.needsRecreate = !thread.hasNativeHistory;
|
|
150
|
-
}
|
|
151
|
-
// A controlled idle restart retains the runtime epoch and all durable thread identities.
|
|
152
|
-
try {
|
|
153
|
-
startPromise = start();
|
|
154
|
-
await startPromise;
|
|
155
|
-
if (closed)
|
|
156
|
-
throw new Error('codex_not_started');
|
|
157
|
-
if (!live)
|
|
158
|
-
throw new NativePreparationError('workspace_skills_unavailable');
|
|
159
|
-
await options.onSkillProcessRestart?.({ pid: live.pid, startedAt: live.startedAt });
|
|
160
|
-
if (closed)
|
|
161
|
-
throw new Error('codex_not_started');
|
|
162
|
-
// Other workspaces resume lazily, so a damaged unrelated lock cannot block this workspace.
|
|
163
|
-
const thread = threads.get(threadId);
|
|
164
|
-
if (thread?.hasNativeHistory && thread.cwd)
|
|
165
|
-
await resumePreparedThread(threadId, thread, await lockedThreadConfig(thread.cwd, selectedConfigId ?? thread.modelConfigId, thread.sessionId));
|
|
166
|
-
}
|
|
167
|
-
catch {
|
|
168
|
-
readyFlag = false;
|
|
169
|
-
skillsRestartFailed = true;
|
|
170
|
-
if (live) {
|
|
171
|
-
live.superseded = true;
|
|
172
|
-
await live.rpc.close();
|
|
173
|
-
await live.child.stop();
|
|
174
|
-
}
|
|
175
|
-
throw new NativePreparationError('workspace_skills_unavailable');
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
async function resumePreparedThread(threadId, thread, prepared) {
|
|
179
|
-
const resumed = await rpc.request('thread/resume', {
|
|
180
|
-
threadId, cwd: thread.cwd, ...prepared.params,
|
|
181
|
-
}, 30_000);
|
|
182
|
-
if (resumed.thread?.id !== threadId)
|
|
183
|
-
throw new NativePreparationError('workspace_skills_unavailable');
|
|
184
|
-
thread.loaded = true;
|
|
185
|
-
thread.skillsHash = prepared.skillsHash;
|
|
186
|
-
thread.discoveryHash = prepared.discoveryHash;
|
|
187
|
-
}
|
|
188
|
-
async function start() {
|
|
189
|
-
if (closed)
|
|
190
|
-
throw new Error('codex_not_started');
|
|
191
|
-
await options.modelBroker?.start();
|
|
192
|
-
if (closed)
|
|
193
|
-
throw new Error('codex_not_started');
|
|
194
|
-
const bin = options.command || process.env.PLAYCRAFT_CODEX_BIN || (launcherSocket ? '/usr/local/bin/codex' : 'codex');
|
|
195
|
-
const { command, prefix } = spawnCommand(bin);
|
|
196
|
-
const launchArgs = [...prefix, ...(options.args ?? ['app-server'])];
|
|
197
|
-
const catalogArgs = launchArgs.at(-1) === 'app-server' ? launchArgs.slice(0, -1) : launchArgs;
|
|
198
|
-
let catalogPath;
|
|
199
|
-
try {
|
|
200
|
-
// Only public model metadata is shared with the development identity.
|
|
201
|
-
// A private, control-owned directory keeps it outside writable project files.
|
|
202
|
-
catalogDirectory ??= await mkdtemp(join(tmpdir(), 'playcraft-codex-catalog-'));
|
|
203
|
-
await chmod(catalogDirectory, 0o755);
|
|
204
|
-
catalogPath = await writePortableCodexCatalog({ directory: catalogDirectory, command, args: catalogArgs });
|
|
205
|
-
}
|
|
206
|
-
catch {
|
|
207
|
-
throw new NativePreparationError('codex_model_catalog_unavailable');
|
|
208
|
-
}
|
|
209
|
-
if (closed)
|
|
210
|
-
throw new Error('codex_not_started');
|
|
211
|
-
const args = [...prefix, ...(options.args ?? ['app-server'])];
|
|
212
|
-
if (launcherSocket) {
|
|
213
|
-
// Thread model access comes from the broker. These overrides carry no proxy address or token.
|
|
214
|
-
for (const [key, value] of Object.entries({
|
|
215
|
-
model,
|
|
216
|
-
approval_policy: 'never',
|
|
217
|
-
sandbox_mode: 'danger-full-access',
|
|
218
|
-
...portableCodexConfig(catalogPath),
|
|
219
|
-
}))
|
|
220
|
-
args.push('-c', `${key}=${JSON.stringify(value)}`);
|
|
221
|
-
child = await startLauncherProcess({
|
|
222
|
-
socketPath: launcherSocket,
|
|
223
|
-
processId: `codex-${randomUUID()}`,
|
|
224
|
-
runtimeEpoch: executionEpoch,
|
|
225
|
-
argv: [command, ...args],
|
|
226
|
-
// The launcher admits the dev home, not the shared /workspaces root.
|
|
227
|
-
// Each thread supplies its registered workspace separately.
|
|
228
|
-
cwd: options.config.workspaceMode === 'multi' ? '/home/playcraft-dev' : options.config.workspaceRoot,
|
|
229
|
-
env: { CODEX_HOME: homeDir },
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
if (closed)
|
|
234
|
-
throw new Error('codex_not_started');
|
|
235
|
-
const local = spawn(command, args, {
|
|
236
|
-
cwd: options.config.workspaceRoot,
|
|
237
|
-
env: { ...process.env, ...options.env, CODEX_HOME: homeDir, PLAYCRAFT_SKIP_UPDATE_CHECK: '1' },
|
|
238
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
239
|
-
detached: options.processGroup === true,
|
|
240
|
-
});
|
|
241
|
-
await new Promise((resolve, reject) => { local.once('spawn', resolve); local.once('error', reject); });
|
|
242
|
-
child = {
|
|
243
|
-
pid: local.pid, stdin: local.stdin, stdout: local.stdout, stderr: local.stderr,
|
|
244
|
-
onExit(listener) { local.once('exit', listener); },
|
|
245
|
-
async stop() {
|
|
246
|
-
if (local.exitCode === null && local.signalCode === null) {
|
|
247
|
-
if (options.processGroup && local.pid) {
|
|
248
|
-
try {
|
|
249
|
-
process.kill(-local.pid, 'SIGTERM');
|
|
250
|
-
}
|
|
251
|
-
catch { /* already stopped */ }
|
|
252
|
-
}
|
|
253
|
-
else
|
|
254
|
-
local.kill('SIGTERM');
|
|
255
|
-
}
|
|
256
|
-
await waitForChildExit(local);
|
|
257
|
-
},
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
if (closed) {
|
|
261
|
-
await child.stop();
|
|
262
|
-
throw new Error('codex_not_started');
|
|
263
|
-
}
|
|
264
|
-
child.stderr?.on('data', (chunk) => {
|
|
265
|
-
process.stderr.write(chunk);
|
|
266
|
-
});
|
|
267
|
-
rpc = createJsonRpcStdioClient(child);
|
|
268
|
-
rpc.onNotification((method, params) => {
|
|
269
|
-
if (method === 'turn/completed') {
|
|
270
|
-
const record = asRecord(params), turnId = asRecord(record.turn).id;
|
|
271
|
-
const thread = typeof record.threadId === 'string' ? threads.get(record.threadId)
|
|
272
|
-
: [...threads.values()].find(item => item.lastTurnId === turnId);
|
|
273
|
-
if (thread) {
|
|
274
|
-
thread.turnActive = false;
|
|
275
|
-
if (typeof turnId === 'string')
|
|
276
|
-
thread.lastCompletedTurnId = turnId;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
emitCollected(collectedEvent('notification', method, params));
|
|
280
|
-
});
|
|
281
|
-
rpc.onServerRequest((id, method, params) => {
|
|
282
|
-
const payload = asRecord(params);
|
|
283
|
-
const approvalId = String(payload.itemId ?? id);
|
|
284
|
-
const turnId = typeof payload.turnId === 'string' ? payload.turnId : '';
|
|
285
|
-
approvals.set(JSON.stringify([String(payload.threadId ?? ''), approvalId]), {
|
|
286
|
-
rpcId: id,
|
|
287
|
-
threadId: String(payload.threadId ?? ''),
|
|
288
|
-
turnId: turnId || undefined,
|
|
289
|
-
});
|
|
290
|
-
emitCollected(collectedEvent('approval_request', method, { ...payload, itemId: approvalId }));
|
|
291
|
-
});
|
|
292
|
-
const pid = child.pid;
|
|
293
|
-
if (!pid)
|
|
294
|
-
throw new Error('codex_pid_missing');
|
|
295
|
-
const current = {
|
|
296
|
-
child,
|
|
297
|
-
rpc,
|
|
298
|
-
pid,
|
|
299
|
-
startedAt: new Date().toISOString(),
|
|
300
|
-
superseded: false,
|
|
301
|
-
};
|
|
302
|
-
live = current;
|
|
303
|
-
child.onExit(() => {
|
|
304
|
-
void current.rpc.close();
|
|
305
|
-
if (live === current) {
|
|
306
|
-
readyFlag = false;
|
|
307
|
-
// Retain the old handle until its entire execution group is confirmed stopped.
|
|
308
|
-
rpc = undefined;
|
|
309
|
-
if (!current.superseded)
|
|
310
|
-
emitCollected(collectedEvent('process_exit', 'process.exited', { pid: current.pid }));
|
|
311
|
-
}
|
|
312
|
-
if (!current.superseded && !closed) {
|
|
313
|
-
for (const listener of exitListeners)
|
|
314
|
-
listener({ pid: current.pid });
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
try {
|
|
318
|
-
await rpc.request('initialize', {
|
|
319
|
-
clientInfo: {
|
|
320
|
-
name: 'playcraft_workspace_runtime',
|
|
321
|
-
title: 'PlayCraft Workspace Runtime',
|
|
322
|
-
version: options.config.cliVersion,
|
|
323
|
-
},
|
|
324
|
-
});
|
|
325
|
-
if (closed)
|
|
326
|
-
throw new Error('codex_not_started');
|
|
327
|
-
rpc.notify('initialized', {});
|
|
328
|
-
readyFlag = true;
|
|
329
|
-
}
|
|
330
|
-
catch (error) {
|
|
331
|
-
current.superseded = true;
|
|
332
|
-
await current.rpc.close();
|
|
333
|
-
await current.child.stop();
|
|
334
|
-
throw error;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return {
|
|
338
|
-
name: 'codex-app-server',
|
|
339
|
-
ready: () => readyFlag && !closed,
|
|
340
|
-
executionProcess() {
|
|
341
|
-
if (!live)
|
|
342
|
-
return undefined;
|
|
343
|
-
return { pid: live.pid, startedAt: live.startedAt };
|
|
344
|
-
},
|
|
345
|
-
onUnexpectedExit(listener) {
|
|
346
|
-
exitListeners.add(listener);
|
|
347
|
-
},
|
|
348
|
-
onCollected(listener) {
|
|
349
|
-
collected.add(listener);
|
|
350
|
-
return () => collected.delete(listener);
|
|
351
|
-
},
|
|
352
|
-
turnStartSends() {
|
|
353
|
-
return rpc?.turnStartSends() ?? 0;
|
|
354
|
-
},
|
|
355
|
-
async replaceProcess() {
|
|
356
|
-
const previous = live;
|
|
357
|
-
if (previous)
|
|
358
|
-
previous.superseded = true;
|
|
359
|
-
readyFlag = false;
|
|
360
|
-
startPromise = undefined;
|
|
361
|
-
threads.clear();
|
|
362
|
-
approvals.clear();
|
|
363
|
-
await previous?.rpc.close().catch(() => undefined);
|
|
364
|
-
if (previous)
|
|
365
|
-
await previous.child.stop();
|
|
366
|
-
executionEpoch += 1;
|
|
367
|
-
skillsRestartFailed = false;
|
|
368
|
-
if (closed)
|
|
369
|
-
throw new Error('codex_not_started');
|
|
370
|
-
try {
|
|
371
|
-
startPromise = start();
|
|
372
|
-
await startPromise;
|
|
373
|
-
}
|
|
374
|
-
catch (error) {
|
|
375
|
-
startPromise = undefined;
|
|
376
|
-
throw error;
|
|
377
|
-
}
|
|
378
|
-
const next = live;
|
|
379
|
-
if (!next)
|
|
380
|
-
throw new Error('codex_not_started');
|
|
381
|
-
return { pid: next.pid, startedAt: next.startedAt };
|
|
382
|
-
},
|
|
383
|
-
async readThread(input) {
|
|
384
|
-
try {
|
|
385
|
-
await ensureStarted();
|
|
386
|
-
if (!rpc)
|
|
387
|
-
return undefined;
|
|
388
|
-
const result = await rpc.request('thread/read', {
|
|
389
|
-
threadId: input.threadId,
|
|
390
|
-
includeTurns: true,
|
|
391
|
-
}, 30_000);
|
|
392
|
-
const projected = projectNativeThread(input.threadId, result.thread ?? result);
|
|
393
|
-
return projected;
|
|
394
|
-
}
|
|
395
|
-
catch {
|
|
396
|
-
return undefined;
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
|
-
async start() {
|
|
400
|
-
await ensureStarted();
|
|
401
|
-
},
|
|
402
|
-
async startThread(input) {
|
|
403
|
-
return withPreparation(async () => {
|
|
404
|
-
await ensureStarted();
|
|
405
|
-
if (!rpc)
|
|
406
|
-
throw new Error('codex_not_started');
|
|
407
|
-
const prepared = await lockedThreadConfig(input.cwd, input.modelConfigId, input.sessionId);
|
|
408
|
-
const result = await rpc.request('thread/start', {
|
|
409
|
-
cwd: input.cwd,
|
|
410
|
-
model,
|
|
411
|
-
...prepared.params,
|
|
412
|
-
});
|
|
413
|
-
const threadId = result.thread?.id;
|
|
414
|
-
if (!threadId)
|
|
415
|
-
throw new Error('native_receipt_lost');
|
|
416
|
-
prepared.activate();
|
|
417
|
-
threads.set(threadId, { sessionId: input.sessionId, cwd: input.cwd, skillsHash: prepared.skillsHash,
|
|
418
|
-
discoveryHash: prepared.discoveryHash, modelConfigId: prepared.modelConfigId, hasNativeHistory: false, loaded: true });
|
|
419
|
-
return { threadId, modelConfigId: prepared.modelConfigId };
|
|
420
|
-
});
|
|
421
|
-
},
|
|
422
|
-
async resumeThread(input) {
|
|
423
|
-
return withPreparation(async () => {
|
|
424
|
-
await ensureStarted();
|
|
425
|
-
if (!rpc)
|
|
426
|
-
throw new Error('codex_not_started');
|
|
427
|
-
const prepared = await lockedThreadConfig(input.cwd, input.modelConfigId, input.sessionId);
|
|
428
|
-
let resumedId;
|
|
429
|
-
try {
|
|
430
|
-
const resumed = await rpc.request('thread/resume', {
|
|
431
|
-
threadId: input.threadId, cwd: input.cwd, ...prepared.params,
|
|
432
|
-
}, 30_000);
|
|
433
|
-
resumedId = resumed.thread?.id;
|
|
434
|
-
}
|
|
435
|
-
catch {
|
|
436
|
-
return undefined;
|
|
437
|
-
}
|
|
438
|
-
if (resumedId !== input.threadId)
|
|
439
|
-
return undefined;
|
|
440
|
-
prepared.activate();
|
|
441
|
-
threads.set(input.threadId, { sessionId: input.sessionId, cwd: input.cwd, skillsHash: prepared.skillsHash,
|
|
442
|
-
discoveryHash: prepared.discoveryHash, modelConfigId: prepared.modelConfigId, hasNativeHistory: true, loaded: true });
|
|
443
|
-
return { modelConfigId: prepared.modelConfigId };
|
|
444
|
-
});
|
|
445
|
-
},
|
|
446
|
-
async startTurn(input) {
|
|
447
|
-
return withPreparation(async () => {
|
|
448
|
-
await ensureStarted();
|
|
449
|
-
if (!rpc)
|
|
450
|
-
throw new Error('codex_not_started');
|
|
451
|
-
if (threads.get(input.threadId)?.sessionId !== input.sessionId) {
|
|
452
|
-
return { outcome: 'failed', error: 'unknown_thread' };
|
|
453
|
-
}
|
|
454
|
-
const thread = threads.get(input.threadId);
|
|
455
|
-
if (thread.turnActive)
|
|
456
|
-
return { outcome: 'failed', error: 'turn_already_active' };
|
|
457
|
-
if (thread.needsRecreate)
|
|
458
|
-
return { outcome: 'failed', error: 'workspace_skills_changed' };
|
|
459
|
-
let selectedModel;
|
|
460
|
-
let modelConfigId = thread.modelConfigId;
|
|
461
|
-
let activateModel = () => { };
|
|
462
|
-
if (thread.cwd) {
|
|
463
|
-
try {
|
|
464
|
-
const prepared = await lockedThreadConfig(thread.cwd, input.modelConfigId ?? thread.modelConfigId, input.sessionId);
|
|
465
|
-
selectedModel = 'model' in prepared.params ? prepared.params.model : undefined;
|
|
466
|
-
modelConfigId = prepared.modelConfigId;
|
|
467
|
-
activateModel = prepared.activate;
|
|
468
|
-
// Codex does not persist an empty thread rollout until its first turn.
|
|
469
|
-
if (thread.skillsHash && !thread.hasNativeHistory) {
|
|
470
|
-
if (prepared.skillsHash !== thread.skillsHash || prepared.discoveryHash !== thread.discoveryHash)
|
|
471
|
-
return { outcome: 'failed', error: 'workspace_skills_changed' };
|
|
472
|
-
}
|
|
473
|
-
else if (thread.skillsHash && (prepared.skillsHash !== thread.skillsHash || prepared.discoveryHash !== thread.discoveryHash)) {
|
|
474
|
-
await restartForSkills(input.threadId, prepared.modelConfigId);
|
|
475
|
-
}
|
|
476
|
-
if (!thread.loaded)
|
|
477
|
-
await resumePreparedThread(input.threadId, thread, prepared);
|
|
478
|
-
thread.skillsHash = prepared.skillsHash;
|
|
479
|
-
thread.discoveryHash = prepared.discoveryHash;
|
|
480
|
-
}
|
|
481
|
-
catch (error) {
|
|
482
|
-
return { outcome: 'failed', error: error instanceof NativePreparationError ? error.message : 'workspace_skills_unavailable' };
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
const requestId = rpc.reserveRequest();
|
|
486
|
-
await input.persistReservation({ nativeRequestId: String(requestId), modelConfigId });
|
|
487
|
-
activateModel();
|
|
488
|
-
thread.modelConfigId = modelConfigId;
|
|
489
|
-
if (input.simulateLostReceipt)
|
|
490
|
-
return { outcome: 'uncertain', threadId: input.threadId };
|
|
491
|
-
try {
|
|
492
|
-
thread.turnActive = true;
|
|
493
|
-
const started = await rpc.sendReserved(requestId, 'turn/start', { threadId: input.threadId, input: [
|
|
494
|
-
{ type: 'text', text: input.text },
|
|
495
|
-
...(input.images ?? []).map((path) => ({ type: 'localImage', path })),
|
|
496
|
-
], ...(selectedModel ? { model: selectedModel } : {}) }, turnTimeoutMs);
|
|
497
|
-
const turnId = started.turn?.id;
|
|
498
|
-
if (!turnId)
|
|
499
|
-
return { outcome: 'uncertain', threadId: input.threadId };
|
|
500
|
-
const acceptedThread = threads.get(input.threadId);
|
|
501
|
-
if (acceptedThread) {
|
|
502
|
-
acceptedThread.lastTurnId = turnId;
|
|
503
|
-
acceptedThread.hasNativeHistory = true;
|
|
504
|
-
acceptedThread.modelConfigId = modelConfigId;
|
|
505
|
-
acceptedThread.turnActive = acceptedThread.lastCompletedTurnId !== turnId;
|
|
506
|
-
}
|
|
507
|
-
return { outcome: 'accepted', threadId: input.threadId, turnId, modelConfigId };
|
|
508
|
-
}
|
|
509
|
-
catch {
|
|
510
|
-
return { outcome: 'uncertain', threadId: input.threadId };
|
|
511
|
-
}
|
|
512
|
-
});
|
|
513
|
-
},
|
|
514
|
-
async interrupt(input) {
|
|
515
|
-
await ensureStarted();
|
|
516
|
-
const thread = threads.get(input.threadId);
|
|
517
|
-
// Nothing is sent when no turn of this session runs here, so that answer is definite.
|
|
518
|
-
if (thread?.sessionId !== input.sessionId || !thread.turnActive)
|
|
519
|
-
return { outcome: 'failed', error: 'turn_not_running' };
|
|
520
|
-
if (!rpc)
|
|
521
|
-
return { outcome: 'uncertain' };
|
|
522
|
-
const turnId = thread.lastTurnId;
|
|
523
|
-
// A turn that is still starting has no id yet; the last one is already over.
|
|
524
|
-
if (!turnId || turnId === thread.lastCompletedTurnId)
|
|
525
|
-
return { outcome: 'uncertain' };
|
|
526
|
-
try {
|
|
527
|
-
await rpc.request('turn/interrupt', { threadId: input.threadId, turnId });
|
|
528
|
-
const cwd = threads.get(input.threadId)?.cwd;
|
|
529
|
-
if (cwd)
|
|
530
|
-
options.modelBroker?.cancelSession(cwd, input.sessionId);
|
|
531
|
-
}
|
|
532
|
-
catch {
|
|
533
|
-
return { outcome: 'uncertain', turnId };
|
|
534
|
-
}
|
|
535
|
-
return { outcome: 'accepted', turnId };
|
|
536
|
-
},
|
|
537
|
-
async respondApproval(input) {
|
|
538
|
-
if (!rpc)
|
|
539
|
-
return { outcome: 'failed', approvalId: input.approvalId };
|
|
540
|
-
const threadId = [...threads].find(([, thread]) => thread.sessionId === input.sessionId)?.[0];
|
|
541
|
-
const key = JSON.stringify([threadId, input.approvalId]);
|
|
542
|
-
const pending = approvals.get(key);
|
|
543
|
-
if (!pending)
|
|
544
|
-
return { outcome: 'failed', approvalId: input.approvalId };
|
|
545
|
-
approvals.delete(key);
|
|
546
|
-
rpc.respond(pending.rpcId, { decision: input.accepted ? 'accept' : 'decline' });
|
|
547
|
-
emitCollected(collectedEvent('notification', 'approval.resolved', {
|
|
548
|
-
approvalId: input.approvalId,
|
|
549
|
-
accepted: input.accepted,
|
|
550
|
-
threadId,
|
|
551
|
-
turnId: pending.turnId,
|
|
552
|
-
}));
|
|
553
|
-
return { outcome: 'accepted', approvalId: input.approvalId, turnId: pending.turnId };
|
|
554
|
-
},
|
|
555
|
-
async close() {
|
|
556
|
-
if (closed)
|
|
557
|
-
return;
|
|
558
|
-
closed = true;
|
|
559
|
-
readyFlag = false;
|
|
560
|
-
if (live)
|
|
561
|
-
live.superseded = true;
|
|
562
|
-
const current = child;
|
|
563
|
-
await rpc?.close().catch(() => undefined);
|
|
564
|
-
rpc = undefined;
|
|
565
|
-
if (current)
|
|
566
|
-
await current.stop();
|
|
567
|
-
child = undefined;
|
|
568
|
-
live = undefined;
|
|
569
|
-
await options.modelBroker?.close();
|
|
570
|
-
if (catalogDirectory)
|
|
571
|
-
await rm(catalogDirectory, { recursive: true, force: true });
|
|
572
|
-
},
|
|
573
|
-
};
|
|
574
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { promisify } from 'node:util';
|
|
6
|
-
const exec = promisify(execFile);
|
|
7
|
-
function tomlString(value) {
|
|
8
|
-
return JSON.stringify(value);
|
|
9
|
-
}
|
|
10
|
-
/** Keep workspace turns portable between native Responses and function-tool conversion. */
|
|
11
|
-
export function portableCodexConfig(catalogPath) {
|
|
12
|
-
return {
|
|
13
|
-
web_search: 'disabled',
|
|
14
|
-
'features.multi_agent': false,
|
|
15
|
-
'features.goals': false,
|
|
16
|
-
'features.apps': false,
|
|
17
|
-
'features.plugins': false,
|
|
18
|
-
'features.code_mode': false,
|
|
19
|
-
'features.code_mode_host': false,
|
|
20
|
-
'features.code_mode_only': false,
|
|
21
|
-
model_reasoning_summary: 'none',
|
|
22
|
-
...(catalogPath ? { model_catalog_json: catalogPath } : {}),
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
/** Retain real model identities and limits while declaring this gateway's portable tools. */
|
|
26
|
-
export function portableCodexCatalog(raw) {
|
|
27
|
-
if (!raw ||
|
|
28
|
-
typeof raw !== 'object' ||
|
|
29
|
-
Array.isArray(raw) ||
|
|
30
|
-
!Array.isArray(raw.models)) {
|
|
31
|
-
throw new Error('codex_model_catalog_invalid');
|
|
32
|
-
}
|
|
33
|
-
const catalog = raw;
|
|
34
|
-
const models = catalog.models.map((value) => {
|
|
35
|
-
if (!value ||
|
|
36
|
-
typeof value !== 'object' ||
|
|
37
|
-
Array.isArray(value) ||
|
|
38
|
-
typeof value.slug !== 'string') {
|
|
39
|
-
throw new Error('codex_model_catalog_invalid');
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
...value,
|
|
43
|
-
tool_mode: null,
|
|
44
|
-
use_responses_lite: false,
|
|
45
|
-
apply_patch_tool_type: null,
|
|
46
|
-
multi_agent_version: null,
|
|
47
|
-
supports_search_tool: false,
|
|
48
|
-
experimental_supported_tools: [],
|
|
49
|
-
default_reasoning_level: null,
|
|
50
|
-
supported_reasoning_levels: [],
|
|
51
|
-
};
|
|
52
|
-
});
|
|
53
|
-
return { ...catalog, models };
|
|
54
|
-
}
|
|
55
|
-
/** The installed binary supplies its own schema; unsupported binaries fail before inference. */
|
|
56
|
-
export async function writePortableCodexCatalog(input) {
|
|
57
|
-
const command = input.command || process.env.PLAYCRAFT_CODEX_BIN || 'codex';
|
|
58
|
-
const prefix = input.args ?? [];
|
|
59
|
-
let body;
|
|
60
|
-
let version;
|
|
61
|
-
try {
|
|
62
|
-
const bundled = await exec(command, [...prefix, 'debug', 'models', '--bundled'], {
|
|
63
|
-
encoding: 'utf8',
|
|
64
|
-
timeout: 10_000,
|
|
65
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
66
|
-
});
|
|
67
|
-
const metadata = await exec(command, [...prefix, '--version'], {
|
|
68
|
-
encoding: 'utf8',
|
|
69
|
-
timeout: 10_000,
|
|
70
|
-
maxBuffer: 64 * 1024,
|
|
71
|
-
});
|
|
72
|
-
version = metadata.stdout.trim();
|
|
73
|
-
body = JSON.stringify(portableCodexCatalog(JSON.parse(bundled.stdout)));
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
// Continuing with an unverified catalog can silently remove every coding tool.
|
|
77
|
-
throw new Error('codex_model_catalog_unavailable');
|
|
78
|
-
}
|
|
79
|
-
await mkdir(input.directory, { recursive: true });
|
|
80
|
-
const path = join(input.directory, 'portable-models.json');
|
|
81
|
-
await writeFile(path, body, { encoding: 'utf8', mode: 0o644 });
|
|
82
|
-
await writeFile(join(input.directory, 'portable-models.version.json'), JSON.stringify({
|
|
83
|
-
version,
|
|
84
|
-
sha256: createHash('sha256').update(body).digest('hex'),
|
|
85
|
-
}), { encoding: 'utf8', mode: 0o644 });
|
|
86
|
-
return path;
|
|
87
|
-
}
|
|
88
|
-
export async function writePlaycraftCodexConfig(input) {
|
|
89
|
-
await mkdir(input.homeDir, { recursive: true, mode: 0o700 });
|
|
90
|
-
const path = join(input.homeDir, 'config.toml');
|
|
91
|
-
const body = [
|
|
92
|
-
`model = ${tomlString(input.model)}`,
|
|
93
|
-
'model_provider = "playcraft"',
|
|
94
|
-
'approval_policy = "never"',
|
|
95
|
-
'sandbox_mode = "danger-full-access"',
|
|
96
|
-
...Object.entries(portableCodexConfig(input.catalogPath)).map(([key, value]) => `${key} = ${JSON.stringify(value)}`),
|
|
97
|
-
'',
|
|
98
|
-
'[model_providers.playcraft]',
|
|
99
|
-
'name = "PlayCraft Model Proxy"',
|
|
100
|
-
`base_url = ${tomlString(input.modelProxyUrl.replace(/\/+$/, ''))}`,
|
|
101
|
-
'env_key = "OPENAI_API_KEY"',
|
|
102
|
-
'wire_api = "responses"',
|
|
103
|
-
'requires_openai_auth = false',
|
|
104
|
-
'',
|
|
105
|
-
].join('\n');
|
|
106
|
-
await writeFile(path, body, { encoding: 'utf8', mode: 0o600 });
|
|
107
|
-
return path;
|
|
108
|
-
}
|