@playdrop/playdrop-cli 0.12.42 → 0.13.1
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/config/client-meta.json +2 -2
- package/dist/apps/index.d.ts +2 -0
- package/dist/apps/index.js +2 -1
- package/dist/apps/loadCheck.d.ts +4 -1
- package/dist/apps/loadCheck.js +40 -10
- package/dist/apps/staticHtml.d.ts +28 -0
- package/dist/apps/staticHtml.js +343 -0
- package/dist/apps/validate.js +2 -1
- package/dist/commands/captureListing.d.ts +1 -1
- package/dist/commands/captureListing.js +4 -3
- package/dist/commands/devServer.js +19 -0
- package/dist/commands/feedback.d.ts +2 -0
- package/dist/commands/feedback.js +44 -2
- package/dist/commands/upload.d.ts +24 -0
- package/dist/commands/upload.js +122 -4
- package/dist/commands/worker/instrument-evidence.js +1 -1
- package/dist/commands/worker/runtime.d.ts +15 -0
- package/dist/commands/worker/runtime.js +121 -11
- package/dist/commands/worker.d.ts +28 -2
- package/dist/commands/worker.js +843 -170
- package/dist/index.js +7 -0
- package/dist/listingPreflight.js +15 -4
- package/dist/recorderRelease.d.ts +18 -0
- package/dist/recorderRelease.js +90 -0
- package/node_modules/@playdrop/api-client/dist/client.d.ts +5 -2
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +3 -2
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +13 -2
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +2 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/me.d.ts +2 -1
- package/node_modules/@playdrop/api-client/dist/domains/me.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/me.js +11 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +6 -3
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +14 -2
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/config/dist/src/constants.d.ts +1 -0
- package/node_modules/@playdrop/config/dist/src/constants.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/constants.js +1 -0
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +195 -4
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +25 -2
- package/node_modules/entities/decode-codepoint.d.ts +19 -0
- package/node_modules/entities/decode-codepoint.d.ts.map +1 -0
- package/node_modules/entities/decode-codepoint.js +77 -0
- package/node_modules/entities/decode-codepoint.js.map +1 -0
- package/node_modules/entities/decode.d.ts +209 -0
- package/node_modules/entities/decode.d.ts.map +1 -0
- package/node_modules/entities/decode.js +511 -0
- package/node_modules/entities/decode.js.map +1 -0
- package/node_modules/entities/encode.d.ts +22 -0
- package/node_modules/entities/encode.d.ts.map +1 -0
- package/node_modules/entities/encode.js +73 -0
- package/node_modules/entities/encode.js.map +1 -0
- package/node_modules/entities/escape.d.ts +43 -0
- package/node_modules/entities/escape.d.ts.map +1 -0
- package/node_modules/entities/escape.js +121 -0
- package/node_modules/entities/escape.js.map +1 -0
- package/node_modules/entities/generated/decode-data-html.d.ts +2 -0
- package/node_modules/entities/generated/decode-data-html.d.ts.map +1 -0
- package/node_modules/entities/generated/decode-data-html.js +10 -0
- package/node_modules/entities/generated/decode-data-html.js.map +1 -0
- package/node_modules/entities/generated/decode-data-xml.d.ts +2 -0
- package/node_modules/entities/generated/decode-data-xml.d.ts.map +1 -0
- package/node_modules/entities/generated/decode-data-xml.js +10 -0
- package/node_modules/entities/generated/decode-data-xml.js.map +1 -0
- package/node_modules/entities/generated/encode-html.d.ts +8 -0
- package/node_modules/entities/generated/encode-html.d.ts.map +1 -0
- package/node_modules/entities/generated/encode-html.js +13 -0
- package/node_modules/entities/generated/encode-html.js.map +1 -0
- package/node_modules/entities/index.d.ts +96 -0
- package/node_modules/entities/index.d.ts.map +1 -0
- package/node_modules/entities/index.js +131 -0
- package/node_modules/entities/index.js.map +1 -0
- package/node_modules/entities/package.json +3 -0
- package/node_modules/parse5/common/doctype.d.ts +4 -0
- package/node_modules/parse5/common/doctype.js +118 -0
- package/node_modules/parse5/common/error-codes.d.ts +67 -0
- package/node_modules/parse5/common/error-codes.js +66 -0
- package/node_modules/parse5/common/foreign-content.d.ts +9 -0
- package/node_modules/parse5/common/foreign-content.js +237 -0
- package/node_modules/parse5/common/html.d.ts +289 -0
- package/node_modules/parse5/common/html.js +528 -0
- package/node_modules/parse5/common/token.d.ts +84 -0
- package/node_modules/parse5/common/token.js +24 -0
- package/node_modules/parse5/common/unicode.d.ts +42 -0
- package/node_modules/parse5/common/unicode.js +70 -0
- package/node_modules/parse5/index.d.ts +71 -0
- package/node_modules/parse5/index.js +54 -0
- package/node_modules/parse5/package.json +1 -0
- package/node_modules/parse5/parser/formatting-element-list.d.ts +36 -0
- package/node_modules/parse5/parser/formatting-element-list.js +114 -0
- package/node_modules/parse5/parser/index.d.ts +221 -0
- package/node_modules/parse5/parser/index.js +3240 -0
- package/node_modules/parse5/parser/open-element-stack.d.ts +53 -0
- package/node_modules/parse5/parser/open-element-stack.js +328 -0
- package/node_modules/parse5/serializer/index.d.ts +60 -0
- package/node_modules/parse5/serializer/index.js +172 -0
- package/node_modules/parse5/tokenizer/index.d.ts +247 -0
- package/node_modules/parse5/tokenizer/index.js +2714 -0
- package/node_modules/parse5/tokenizer/preprocessor.d.ts +36 -0
- package/node_modules/parse5/tokenizer/preprocessor.js +200 -0
- package/node_modules/parse5/tree-adapters/default.d.ts +84 -0
- package/node_modules/parse5/tree-adapters/default.js +176 -0
- package/node_modules/parse5/tree-adapters/interface.d.ts +255 -0
- package/node_modules/parse5/tree-adapters/interface.js +2 -0
- package/package.json +8 -1
package/dist/commands/worker.js
CHANGED
|
@@ -3,7 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.WORKER_TASK_WORKSPACE_RETENTION = exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = exports.WORKER_SESSION_EXPIRED_MESSAGE = exports.runLoggedProcess = exports.readEnvBoolean = exports.readCodexSandboxMode = exports.readCodexRolloutTokenUsageForThread = exports.extractCodexTokensUsed = exports.extractCodexThreadId = exports.extractCodexRolloutTokenUsage = exports.extractAgentTokenUsage = exports.DEFAULT_WORKER_TOKEN_CAP = exports.DEFAULT_CODEX_TIMEOUT_MS = exports.buildWorkerChildEnv = exports.buildPlaywrightMcpCommand = exports.buildCodexExecArgs = exports.buildClaudePermissionSettings = exports.buildClaudeExecArgs = exports.assertWorkerTokenUsageWithinCap = void 0;
|
|
6
|
+
exports.WORKER_TASK_WORKSPACE_RETENTION = exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = exports.WORKER_SESSION_EXPIRED_MESSAGE = exports.runLoggedProcess = exports.readEnvBoolean = exports.readCodexSandboxMode = exports.readCodexRolloutTokenUsageForThread = exports.extractCodexTokensUsed = exports.extractCodexThreadId = exports.extractClaudeSessionId = exports.extractCodexRolloutTokenUsage = exports.extractAgentTokenUsage = exports.DEFAULT_WORKER_TOKEN_CAP = exports.DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS = exports.DEFAULT_CREATOR_AGENT_TIMEOUT_MS = exports.DEFAULT_CODEX_TIMEOUT_MS = exports.buildWorkerChildEnv = exports.buildPlaywrightMcpCommand = exports.buildCodexExecArgs = exports.buildCleanRoomChildEnv = exports.buildClaudePermissionSettings = exports.buildClaudeExecArgs = exports.assertWorkerTokenUsageWithinCap = void 0;
|
|
7
|
+
exports.usesCreatorRecovery = usesCreatorRecovery;
|
|
8
|
+
exports.buildCreatorRecoveryPrompt = buildCreatorRecoveryPrompt;
|
|
9
|
+
exports.buildGameEvalRetryPrompt = buildGameEvalRetryPrompt;
|
|
7
10
|
exports.allocateWorkerDevPort = allocateWorkerDevPort;
|
|
8
11
|
exports.isWorkerContextCommandAllowed = isWorkerContextCommandAllowed;
|
|
9
12
|
exports.resolveWorkerExecutionTargetFromRole = resolveWorkerExecutionTargetFromRole;
|
|
@@ -12,6 +15,7 @@ exports.nextClaimBackoffMs = nextClaimBackoffMs;
|
|
|
12
15
|
exports.claimBackoffDelayMs = claimBackoffDelayMs;
|
|
13
16
|
exports.resolveWorkerHomeDir = resolveWorkerHomeDir;
|
|
14
17
|
exports.pruneWorkerTaskWorkspaces = pruneWorkerTaskWorkspaces;
|
|
18
|
+
exports.prepareClaimedTaskWorkspace = prepareClaimedTaskWorkspace;
|
|
15
19
|
exports.resolveWorkerInstrumentSurface = resolveWorkerInstrumentSurface;
|
|
16
20
|
exports.hasAgentTaskUploadedArtifact = hasAgentTaskUploadedArtifact;
|
|
17
21
|
exports.appendWorkerTranscriptChunks = appendWorkerTranscriptChunks;
|
|
@@ -19,6 +23,7 @@ exports.readTaskNextStepsFile = readTaskNextStepsFile;
|
|
|
19
23
|
exports.assertTaskUploadResultMatchesContext = assertTaskUploadResultMatchesContext;
|
|
20
24
|
exports.stageAssignmentWorkspaceV2 = stageAssignmentWorkspaceV2;
|
|
21
25
|
exports.assertAssignmentPluginBundleExtractionBounds = assertAssignmentPluginBundleExtractionBounds;
|
|
26
|
+
exports.stageDevPluginWorkingTree = stageDevPluginWorkingTree;
|
|
22
27
|
exports.stageAssignmentPluginBundle = stageAssignmentPluginBundle;
|
|
23
28
|
exports.resolveDevPluginWorkingTree = resolveDevPluginWorkingTree;
|
|
24
29
|
exports.discoverWorkerProjectRoot = discoverWorkerProjectRoot;
|
|
@@ -43,6 +48,7 @@ exports.prepareCodexRunHomes = prepareCodexRunHomes;
|
|
|
43
48
|
exports.prepareClaudeRunConfig = prepareClaudeRunConfig;
|
|
44
49
|
exports.removeAgentRunCredentials = removeAgentRunCredentials;
|
|
45
50
|
exports.removeStaleAgentRunCredentials = removeStaleAgentRunCredentials;
|
|
51
|
+
exports.mergeCreatorRecoveryResults = mergeCreatorRecoveryResults;
|
|
46
52
|
exports.buildAgentFailureCode = buildAgentFailureCode;
|
|
47
53
|
exports.describeAgentFailureForEvent = describeAgentFailureForEvent;
|
|
48
54
|
exports.createLocalPlaydropShim = createLocalPlaydropShim;
|
|
@@ -50,6 +56,7 @@ exports.parseCodexModelCatalog = parseCodexModelCatalog;
|
|
|
50
56
|
exports.parseClaudeModelCatalog = parseClaudeModelCatalog;
|
|
51
57
|
exports.claudeModelProbeReportsSelection = claudeModelProbeReportsSelection;
|
|
52
58
|
exports.probeWorkerProviderIndependently = probeWorkerProviderIndependently;
|
|
59
|
+
exports.describeWorkerPreflightReason = describeWorkerPreflightReason;
|
|
53
60
|
exports.buildWorkerSetupActions = buildWorkerSetupActions;
|
|
54
61
|
exports.resolveWorkerStateDir = resolveWorkerStateDir;
|
|
55
62
|
exports.resolveWorkerSupervisorLockFile = resolveWorkerSupervisorLockFile;
|
|
@@ -71,6 +78,7 @@ exports.showWorkerStatus = showWorkerStatus;
|
|
|
71
78
|
exports.setupWorker = setupWorker;
|
|
72
79
|
exports.instrumentTaskChromeTitleMatchers = instrumentTaskChromeTitleMatchers;
|
|
73
80
|
exports.enforceInstrumentBrowserHygiene = enforceInstrumentBrowserHygiene;
|
|
81
|
+
exports.assignmentRequiresRegisteredBundle = assignmentRequiresRegisteredBundle;
|
|
74
82
|
exports.startWorker = startWorker;
|
|
75
83
|
exports.reportTask = reportTask;
|
|
76
84
|
exports.showTaskContext = showTaskContext;
|
|
@@ -101,6 +109,7 @@ const commandContext_1 = require("../commandContext");
|
|
|
101
109
|
const config_1 = require("../config");
|
|
102
110
|
const messages_1 = require("../messages");
|
|
103
111
|
const output_1 = require("../output");
|
|
112
|
+
const recorderRelease_1 = require("../recorderRelease");
|
|
104
113
|
const shellProbe_1 = require("../shellProbe");
|
|
105
114
|
const review_1 = require("./review");
|
|
106
115
|
const upload_1 = require("./upload");
|
|
@@ -110,13 +119,17 @@ var runtime_2 = require("./worker/runtime");
|
|
|
110
119
|
Object.defineProperty(exports, "assertWorkerTokenUsageWithinCap", { enumerable: true, get: function () { return runtime_2.assertWorkerTokenUsageWithinCap; } });
|
|
111
120
|
Object.defineProperty(exports, "buildClaudeExecArgs", { enumerable: true, get: function () { return runtime_2.buildClaudeExecArgs; } });
|
|
112
121
|
Object.defineProperty(exports, "buildClaudePermissionSettings", { enumerable: true, get: function () { return runtime_2.buildClaudePermissionSettings; } });
|
|
122
|
+
Object.defineProperty(exports, "buildCleanRoomChildEnv", { enumerable: true, get: function () { return runtime_2.buildCleanRoomChildEnv; } });
|
|
113
123
|
Object.defineProperty(exports, "buildCodexExecArgs", { enumerable: true, get: function () { return runtime_2.buildCodexExecArgs; } });
|
|
114
124
|
Object.defineProperty(exports, "buildPlaywrightMcpCommand", { enumerable: true, get: function () { return runtime_2.buildPlaywrightMcpCommand; } });
|
|
115
125
|
Object.defineProperty(exports, "buildWorkerChildEnv", { enumerable: true, get: function () { return runtime_2.buildWorkerChildEnv; } });
|
|
116
126
|
Object.defineProperty(exports, "DEFAULT_CODEX_TIMEOUT_MS", { enumerable: true, get: function () { return runtime_2.DEFAULT_CODEX_TIMEOUT_MS; } });
|
|
127
|
+
Object.defineProperty(exports, "DEFAULT_CREATOR_AGENT_TIMEOUT_MS", { enumerable: true, get: function () { return runtime_2.DEFAULT_CREATOR_AGENT_TIMEOUT_MS; } });
|
|
128
|
+
Object.defineProperty(exports, "DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS", { enumerable: true, get: function () { return runtime_2.DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS; } });
|
|
117
129
|
Object.defineProperty(exports, "DEFAULT_WORKER_TOKEN_CAP", { enumerable: true, get: function () { return runtime_2.DEFAULT_WORKER_TOKEN_CAP; } });
|
|
118
130
|
Object.defineProperty(exports, "extractAgentTokenUsage", { enumerable: true, get: function () { return runtime_2.extractAgentTokenUsage; } });
|
|
119
131
|
Object.defineProperty(exports, "extractCodexRolloutTokenUsage", { enumerable: true, get: function () { return runtime_2.extractCodexRolloutTokenUsage; } });
|
|
132
|
+
Object.defineProperty(exports, "extractClaudeSessionId", { enumerable: true, get: function () { return runtime_2.extractClaudeSessionId; } });
|
|
120
133
|
Object.defineProperty(exports, "extractCodexThreadId", { enumerable: true, get: function () { return runtime_2.extractCodexThreadId; } });
|
|
121
134
|
Object.defineProperty(exports, "extractCodexTokensUsed", { enumerable: true, get: function () { return runtime_2.extractCodexTokensUsed; } });
|
|
122
135
|
Object.defineProperty(exports, "readCodexRolloutTokenUsageForThread", { enumerable: true, get: function () { return runtime_2.readCodexRolloutTokenUsageForThread; } });
|
|
@@ -137,7 +150,9 @@ const FAIL_REPORT_RETRY_DELAY_MS = 2000;
|
|
|
137
150
|
const WORKER_TRANSCRIPT_CHUNK_BATCH_SIZE = 100;
|
|
138
151
|
const SLACK_API_BASE_URL = 'https://slack.com/api';
|
|
139
152
|
const WORKER_WRAPPER_CONTRACT_VERSION = 2;
|
|
140
|
-
const WORKER_SUPPORTED_KINDS = ['NEW_GAME', 'REMIX_GAME', 'GAME_UPDATE', 'GAME_REVIEW', 'GAME_EVAL'];
|
|
153
|
+
const WORKER_SUPPORTED_KINDS = ['NEW_GAME', 'STATIC_GAME', 'REMIX_GAME', 'GAME_UPDATE', 'GAME_REVIEW', 'GAME_EVAL'];
|
|
154
|
+
const GAME_CREATION_TASK_KINDS = new Set(['NEW_GAME', 'STATIC_GAME', 'REMIX_GAME', 'GAME_UPDATE']);
|
|
155
|
+
const AGENT_MANAGED_UPLOAD_TASK_KINDS = new Set(['NEW_GAME', 'REMIX_GAME', 'GAME_UPDATE']);
|
|
141
156
|
const requireFromWorker = (0, node_module_1.createRequire)(__filename);
|
|
142
157
|
const AGENT_BUNDLE_MANIFEST_PATH = 'agent-bundle.json';
|
|
143
158
|
const ASSIGNMENT_PLUGIN_BUNDLE_MAX_FILES = 1000;
|
|
@@ -148,6 +163,36 @@ const ZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50;
|
|
|
148
163
|
const ZIP_END_OF_CENTRAL_DIRECTORY_MIN_BYTES = 22;
|
|
149
164
|
const ZIP_MAX_COMMENT_BYTES = 0xffff;
|
|
150
165
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
166
|
+
function usesCreatorRecovery(kind) {
|
|
167
|
+
return GAME_CREATION_TASK_KINDS.has(kind);
|
|
168
|
+
}
|
|
169
|
+
function buildCreatorRecoveryPrompt(input) {
|
|
170
|
+
const prompt = input.prompt.trim();
|
|
171
|
+
if (!prompt) {
|
|
172
|
+
throw new Error('agent_task_assignment_prompt_missing');
|
|
173
|
+
}
|
|
174
|
+
if (!GAME_CREATION_TASK_KINDS.has(input.kind)) {
|
|
175
|
+
throw new Error(`agent_task_creation_recovery_unsupported:${input.kind}`);
|
|
176
|
+
}
|
|
177
|
+
const finishInstruction = AGENT_MANAGED_UPLOAD_TASK_KINDS.has(input.kind)
|
|
178
|
+
? 'Prioritize `playdrop task upload` and then `playdrop task done`.'
|
|
179
|
+
: 'Finish a valid standalone `index.html` at the project root, then stop so the worker can publish it.';
|
|
180
|
+
return [
|
|
181
|
+
'This task was paused because it was taking too long. Continue from the existing workspace and do not restart or redesign the game.',
|
|
182
|
+
'You have 10 minutes to finish the strongest solid version already present. Make only the smallest fixes required for validation and publishing.',
|
|
183
|
+
`${finishInstruction} Do not add features or polish. If completion cannot succeed, report the exact blocker and stop.`,
|
|
184
|
+
`Original request: ${prompt}`,
|
|
185
|
+
].join('\n\n');
|
|
186
|
+
}
|
|
187
|
+
function buildGameEvalRetryPrompt(prompt) {
|
|
188
|
+
return [
|
|
189
|
+
prompt,
|
|
190
|
+
'',
|
|
191
|
+
'This is a retry in the existing evaluation workspace.',
|
|
192
|
+
'Reuse the existing result.json and evidence. Do not replay completed game checks unless required evidence is missing or invalid.',
|
|
193
|
+
'First correct any deterministic submission-contract error, then submit the existing result and evidence and finish.',
|
|
194
|
+
].join('\n');
|
|
195
|
+
}
|
|
151
196
|
exports.WORKER_SESSION_EXPIRED_MESSAGE = 'worker session expired: run "playdrop auth login" and start the worker again';
|
|
152
197
|
const WORKER_CONTEXT_ALLOWED_COMMANDS = [
|
|
153
198
|
['task', 'context'],
|
|
@@ -505,6 +550,29 @@ async function pruneWorkerTaskWorkspaces(incomingWorkspaceDir, retention = expor
|
|
|
505
550
|
}
|
|
506
551
|
return removed;
|
|
507
552
|
}
|
|
553
|
+
async function prepareClaimedTaskWorkspace(input) {
|
|
554
|
+
await pruneWorkerTaskWorkspaces(input.workspaceDir);
|
|
555
|
+
const resumed = input.attempt > 1 && (0, node_fs_1.existsSync)(input.workspaceDir);
|
|
556
|
+
if (!resumed) {
|
|
557
|
+
await (0, promises_1.rm)(input.workspaceDir, { recursive: true, force: true });
|
|
558
|
+
await (0, promises_1.mkdir)(input.workspaceDir, { recursive: true });
|
|
559
|
+
return { resumed: false };
|
|
560
|
+
}
|
|
561
|
+
await removeAgentRunCredentials(input.workspaceDir);
|
|
562
|
+
const retainedProjectBindings = [
|
|
563
|
+
node_path_1.default.join(input.workspaceDir, '.playdrop.json'),
|
|
564
|
+
...(0, node_fs_1.readdirSync)(input.workspaceDir, { withFileTypes: true })
|
|
565
|
+
.filter((entry) => entry.isDirectory() && entry.name !== '.playdrop')
|
|
566
|
+
.map((entry) => node_path_1.default.join(input.workspaceDir, entry.name, '.playdrop.json')),
|
|
567
|
+
];
|
|
568
|
+
await Promise.all([
|
|
569
|
+
(0, promises_1.rm)(node_path_1.default.join(input.workspaceDir, '.playdrop', 'task-events'), { recursive: true, force: true }),
|
|
570
|
+
(0, promises_1.rm)(node_path_1.default.join(input.workspaceDir, '.playdrop', 'task-upload.lock'), { force: true }),
|
|
571
|
+
(0, promises_1.rm)(node_path_1.default.join(input.workspaceDir, '.playdrop', 'task-upload-result.json'), { force: true }),
|
|
572
|
+
...retainedProjectBindings.map((file) => (0, promises_1.rm)(file, { force: true })),
|
|
573
|
+
]);
|
|
574
|
+
return { resumed: true };
|
|
575
|
+
}
|
|
508
576
|
function buildWorkerTaskContext(input) {
|
|
509
577
|
const playdrop = input.metadata.playdrop;
|
|
510
578
|
if (!playdrop || typeof playdrop !== 'object' || Array.isArray(playdrop)) {
|
|
@@ -512,7 +580,7 @@ function buildWorkerTaskContext(input) {
|
|
|
512
580
|
}
|
|
513
581
|
const metadata = playdrop;
|
|
514
582
|
const kind = metadata.kind;
|
|
515
|
-
if (kind !== 'NEW_GAME' && kind !== 'REMIX_GAME' && kind !== 'GAME_UPDATE' && kind !== 'GAME_REVIEW' && kind !== 'GAME_EVAL') {
|
|
583
|
+
if (kind !== 'NEW_GAME' && kind !== 'STATIC_GAME' && kind !== 'REMIX_GAME' && kind !== 'GAME_UPDATE' && kind !== 'GAME_REVIEW' && kind !== 'GAME_EVAL') {
|
|
516
584
|
throw new Error('agent_task_context_kind_missing');
|
|
517
585
|
}
|
|
518
586
|
const creatorUsername = typeof metadata.creatorUsername === 'string'
|
|
@@ -583,6 +651,23 @@ function resolveWorkerInstrumentSurface(taskContext) {
|
|
|
583
651
|
return null;
|
|
584
652
|
}
|
|
585
653
|
const metadata = playdrop;
|
|
654
|
+
if (taskContext.kind === 'STATIC_GAME') {
|
|
655
|
+
const surfaceContext = metadata.surfaceContext;
|
|
656
|
+
if (!surfaceContext || typeof surfaceContext !== 'object' || Array.isArray(surfaceContext)) {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
const context = surfaceContext;
|
|
660
|
+
if (context.deviceClass === 'DESKTOP') {
|
|
661
|
+
return 'DESKTOP';
|
|
662
|
+
}
|
|
663
|
+
if (context.orientation === 'PORTRAIT') {
|
|
664
|
+
return 'MOBILE_PORTRAIT';
|
|
665
|
+
}
|
|
666
|
+
if (context.orientation === 'LANDSCAPE') {
|
|
667
|
+
return 'MOBILE_LANDSCAPE';
|
|
668
|
+
}
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
586
671
|
if (taskContext.kind === 'GAME_REVIEW') {
|
|
587
672
|
const reviewTarget = metadata.reviewTarget;
|
|
588
673
|
if (!reviewTarget || typeof reviewTarget !== 'object' || Array.isArray(reviewTarget)) {
|
|
@@ -1073,17 +1158,19 @@ function readAgentBundleManifest(extractedRoot) {
|
|
|
1073
1158
|
}
|
|
1074
1159
|
const manifest = parsed;
|
|
1075
1160
|
if (manifest.version !== 1
|
|
1076
|
-
|| !manifest
|
|
1077
|
-
|| typeof manifest.plugin
|
|
1161
|
+
|| !Object.prototype.hasOwnProperty.call(manifest, 'plugin')
|
|
1162
|
+
|| (manifest.plugin !== null && typeof manifest.plugin?.root !== 'string')
|
|
1078
1163
|
|| !manifest.workspace
|
|
1079
1164
|
|| !Array.isArray(manifest.workspace.files)) {
|
|
1080
1165
|
throw new Error('agent_bundle_manifest_invalid');
|
|
1081
1166
|
}
|
|
1082
|
-
const pluginRoot =
|
|
1083
|
-
|
|
1167
|
+
const pluginRoot = manifest.plugin === null
|
|
1168
|
+
? null
|
|
1169
|
+
: normalizeAgentBundlePath(extractedRoot, manifest.plugin?.root, 'agent_bundle_plugin_root_invalid');
|
|
1170
|
+
if (pluginRoot && !isFile(node_path_1.default.join(pluginRoot, '.codex-plugin', 'plugin.json'))) {
|
|
1084
1171
|
throw new Error('agent_bundle_codex_plugin_manifest_missing');
|
|
1085
1172
|
}
|
|
1086
|
-
if (!isFile(node_path_1.default.join(pluginRoot, '.claude-plugin', 'plugin.json'))) {
|
|
1173
|
+
if (pluginRoot && !isFile(node_path_1.default.join(pluginRoot, '.claude-plugin', 'plugin.json'))) {
|
|
1087
1174
|
throw new Error('agent_bundle_claude_plugin_manifest_missing');
|
|
1088
1175
|
}
|
|
1089
1176
|
const destinations = new Set();
|
|
@@ -1146,6 +1233,19 @@ async function stageAgentBundleWorkspaceFiles(extractedRoot, workspaceDir, taskK
|
|
|
1146
1233
|
}
|
|
1147
1234
|
return resolved.pluginRoot;
|
|
1148
1235
|
}
|
|
1236
|
+
async function stageDevPluginWorkingTree(input) {
|
|
1237
|
+
const sourceRoot = node_path_1.default.resolve(input.workingTree);
|
|
1238
|
+
readAgentBundleManifest(sourceRoot);
|
|
1239
|
+
const snapshotRoot = node_path_1.default.join(input.workspaceDir, '.playdrop', 'dev-task-plugin');
|
|
1240
|
+
await (0, promises_1.rm)(snapshotRoot, { recursive: true, force: true });
|
|
1241
|
+
await (0, promises_1.mkdir)(node_path_1.default.dirname(snapshotRoot), { recursive: true });
|
|
1242
|
+
await (0, promises_1.cp)(sourceRoot, snapshotRoot, { recursive: true });
|
|
1243
|
+
const pluginRoot = await stageAgentBundleWorkspaceFiles(snapshotRoot, input.workspaceDir, input.taskKind);
|
|
1244
|
+
if (!pluginRoot) {
|
|
1245
|
+
throw new Error('worker_dev_plugin_working_tree_requires_plugin');
|
|
1246
|
+
}
|
|
1247
|
+
return pluginRoot;
|
|
1248
|
+
}
|
|
1149
1249
|
async function stageAssignmentPluginBundle(input) {
|
|
1150
1250
|
const sha256 = typeof input.pluginBundle.sha256 === 'string' ? input.pluginBundle.sha256.trim() : '';
|
|
1151
1251
|
const fileName = typeof input.pluginBundle.fileName === 'string' ? input.pluginBundle.fileName.trim() : '';
|
|
@@ -1168,7 +1268,7 @@ async function stageAssignmentPluginBundle(input) {
|
|
|
1168
1268
|
throw new Error('agent_task_plugin_bundle_sha_mismatch');
|
|
1169
1269
|
}
|
|
1170
1270
|
assertAssignmentPluginBundleExtractionBounds(buffer);
|
|
1171
|
-
const pluginRoot = node_path_1.default.join(input.workspaceDir, '.playdrop', 'task-plugin');
|
|
1271
|
+
const pluginRoot = node_path_1.default.join(input.extractionDir ?? input.workspaceDir, '.playdrop', 'task-plugin');
|
|
1172
1272
|
await (0, promises_1.rm)(pluginRoot, { recursive: true, force: true });
|
|
1173
1273
|
await (0, promises_1.mkdir)(pluginRoot, { recursive: true });
|
|
1174
1274
|
const entries = (0, fflate_1.unzipSync)(new Uint8Array(buffer));
|
|
@@ -1215,7 +1315,9 @@ function resolveDevPluginWorkingTree(input) {
|
|
|
1215
1315
|
throw new Error(`worker_dev_plugin_working_tree_requires_dev_environment:${input.env}`);
|
|
1216
1316
|
}
|
|
1217
1317
|
const resolved = node_path_1.default.resolve(workingTree);
|
|
1218
|
-
readAgentBundleManifest(resolved)
|
|
1318
|
+
if (!readAgentBundleManifest(resolved).pluginRoot) {
|
|
1319
|
+
throw new Error('worker_dev_plugin_working_tree_requires_plugin');
|
|
1320
|
+
}
|
|
1219
1321
|
return resolved;
|
|
1220
1322
|
}
|
|
1221
1323
|
// A PlayDrop project root is the topmost directory holding a catalogue.json,
|
|
@@ -1448,14 +1550,6 @@ function readCodexAuthenticated(homeDir = node_os_1.default.homedir()) {
|
|
|
1448
1550
|
return false;
|
|
1449
1551
|
}
|
|
1450
1552
|
}
|
|
1451
|
-
function probeNpmVersion() {
|
|
1452
|
-
const result = (0, shellProbe_1.runShell)('npm --version');
|
|
1453
|
-
const version = result.output.trim().split('\n')[0]?.trim() ?? '';
|
|
1454
|
-
if (result.exitCode !== 0 || !version) {
|
|
1455
|
-
throw new Error(`worker_preflight_npm_missing: "npm --version" exited with code ${result.exitCode}.`);
|
|
1456
|
-
}
|
|
1457
|
-
return version;
|
|
1458
|
-
}
|
|
1459
1553
|
function probePlaywrightChromium() {
|
|
1460
1554
|
let playwrightCore;
|
|
1461
1555
|
let packageJson;
|
|
@@ -1900,11 +1994,15 @@ async function prepareCodexRunHomes(input) {
|
|
|
1900
1994
|
const runRoot = node_path_1.default.join(input.workspaceDir, '.playdrop', 'codex-run');
|
|
1901
1995
|
const homeDir = node_path_1.default.join(runRoot, 'home');
|
|
1902
1996
|
const codexHomeDir = node_path_1.default.join(runRoot, 'codex-home');
|
|
1903
|
-
|
|
1997
|
+
if (!input.preserveExisting) {
|
|
1998
|
+
await (0, promises_1.rm)(runRoot, { recursive: true, force: true });
|
|
1999
|
+
}
|
|
1904
2000
|
await (0, promises_1.mkdir)(runRoot, { recursive: true, mode: 0o700 });
|
|
1905
2001
|
await (0, promises_1.chmod)(runRoot, 0o700);
|
|
1906
|
-
await (0, promises_1.mkdir)(homeDir, { mode: 0o700 });
|
|
1907
|
-
await (0, promises_1.
|
|
2002
|
+
await (0, promises_1.mkdir)(homeDir, { recursive: true, mode: 0o700 });
|
|
2003
|
+
await (0, promises_1.chmod)(homeDir, 0o700);
|
|
2004
|
+
await (0, promises_1.mkdir)(codexHomeDir, { recursive: true, mode: 0o700 });
|
|
2005
|
+
await (0, promises_1.chmod)(codexHomeDir, 0o700);
|
|
1908
2006
|
const sourceCodexHome = node_path_1.default.resolve(input.sourceCodexHome?.trim()
|
|
1909
2007
|
|| node_process_1.default.env.CODEX_HOME?.trim()
|
|
1910
2008
|
|| node_path_1.default.join(node_os_1.default.homedir(), '.codex'));
|
|
@@ -1915,12 +2013,14 @@ async function prepareCodexRunHomes(input) {
|
|
|
1915
2013
|
const stagedAuthPath = node_path_1.default.join(codexHomeDir, 'auth.json');
|
|
1916
2014
|
await (0, promises_1.copyFile)(sourceAuthPath, stagedAuthPath);
|
|
1917
2015
|
await (0, promises_1.chmod)(stagedAuthPath, 0o600);
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2016
|
+
if (input.stagePlaydropSession !== false) {
|
|
2017
|
+
await stageWorkerPlaydropSession({
|
|
2018
|
+
homeDir,
|
|
2019
|
+
workerUsername: input.workerUsername,
|
|
2020
|
+
envName: input.envName,
|
|
2021
|
+
playdropConfig: input.playdropConfig,
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
1924
2024
|
return { homeDir, codexHomeDir };
|
|
1925
2025
|
}
|
|
1926
2026
|
async function stageWorkerPlaydropSession(input) {
|
|
@@ -1999,11 +2099,13 @@ async function removeStaleAgentRunCredentials(workerHomeDir = resolveWorkerHomeD
|
|
|
1999
2099
|
}
|
|
2000
2100
|
async function runCodex(input) {
|
|
2001
2101
|
const { homeDir, codexHomeDir } = await prepareCodexRunHomes({
|
|
2002
|
-
workspaceDir: input.workspaceDir,
|
|
2102
|
+
workspaceDir: input.stateDir ?? input.workspaceDir,
|
|
2003
2103
|
workerUsername: input.workerUsername,
|
|
2004
2104
|
envName: input.envName,
|
|
2105
|
+
stagePlaydropSession: !input.cleanRoom,
|
|
2106
|
+
preserveExisting: Boolean(input.resumeSessionId),
|
|
2005
2107
|
});
|
|
2006
|
-
if (input.taskPluginRoot) {
|
|
2108
|
+
if (input.taskPluginRoot && !input.resumeSessionId) {
|
|
2007
2109
|
const pluginEnv = {
|
|
2008
2110
|
...node_process_1.default.env,
|
|
2009
2111
|
HOME: homeDir,
|
|
@@ -2018,7 +2120,7 @@ async function runCodex(input) {
|
|
|
2018
2120
|
env: pluginEnv,
|
|
2019
2121
|
});
|
|
2020
2122
|
}
|
|
2021
|
-
if (input.enablePlaywrightMcp) {
|
|
2123
|
+
if (input.enablePlaywrightMcp && !input.resumeSessionId) {
|
|
2022
2124
|
const server = (0, runtime_1.buildPlaywrightMcpCommand)(input.instrumentSurface);
|
|
2023
2125
|
await execFileAsync('codex', ['mcp', 'add', 'playwright', '--', server.command, ...server.args], {
|
|
2024
2126
|
cwd: input.workspaceDir,
|
|
@@ -2040,12 +2142,17 @@ async function runCodex(input) {
|
|
|
2040
2142
|
workspaceDir: input.workspaceDir,
|
|
2041
2143
|
model: input.codexModel.model,
|
|
2042
2144
|
reasoningEffort: input.codexModel.reasoningEffort,
|
|
2043
|
-
networkAccess: (0, runtime_1.readEnvBoolean)('PLAYDROP_WORKER_AGENT_NETWORK_ACCESS', false),
|
|
2145
|
+
networkAccess: input.cleanRoom || (0, runtime_1.readEnvBoolean)('PLAYDROP_WORKER_AGENT_NETWORK_ACCESS', false),
|
|
2044
2146
|
sandboxMode: (0, runtime_1.readCodexSandboxMode)(),
|
|
2045
2147
|
imagePaths: input.imagePaths,
|
|
2148
|
+
resumeSessionId: input.resumeSessionId,
|
|
2046
2149
|
}),
|
|
2047
2150
|
cwd: input.workspaceDir,
|
|
2048
|
-
env: (0, runtime_1.
|
|
2151
|
+
env: input.cleanRoom ? (0, runtime_1.buildCleanRoomChildEnv)({
|
|
2152
|
+
binDir: input.binDir,
|
|
2153
|
+
homeDir,
|
|
2154
|
+
codexHomeDir,
|
|
2155
|
+
}) : (0, runtime_1.buildWorkerChildEnv)({
|
|
2049
2156
|
binDir: input.binDir,
|
|
2050
2157
|
taskId: input.taskId,
|
|
2051
2158
|
taskToken: input.taskToken,
|
|
@@ -2058,7 +2165,7 @@ async function runCodex(input) {
|
|
|
2058
2165
|
playwrightBrowsersPath: resolveWorkerPlaywrightBrowsersPath(),
|
|
2059
2166
|
}),
|
|
2060
2167
|
stdin: input.prompt,
|
|
2061
|
-
timeoutMs: (0, runtime_1.readPositiveEnvInt)('PLAYDROP_WORKER_CODEX_TIMEOUT_MS', runtime_1.DEFAULT_CODEX_TIMEOUT_MS),
|
|
2168
|
+
timeoutMs: input.timeoutMs ?? (0, runtime_1.readPositiveEnvInt)('PLAYDROP_WORKER_CODEX_TIMEOUT_MS', runtime_1.DEFAULT_CODEX_TIMEOUT_MS),
|
|
2062
2169
|
maxOutputChars: runtime_1.DEFAULT_CODEX_LOG_TAIL_CHARS,
|
|
2063
2170
|
onTranscriptChunks: async (chunks) => {
|
|
2064
2171
|
if (!observedCodexThreadId) {
|
|
@@ -2080,16 +2187,17 @@ async function runCodex(input) {
|
|
|
2080
2187
|
if (result.tokenUsage.usageParseError) {
|
|
2081
2188
|
console.warn('Codex thread id was not observed; rollout token usage could not be read.');
|
|
2082
2189
|
}
|
|
2083
|
-
return result;
|
|
2190
|
+
return { ...result, providerSessionId: null };
|
|
2084
2191
|
}
|
|
2085
2192
|
const rolloutUsage = await (0, runtime_1.readCodexRolloutTokenUsageForThread)(observedCodexThreadId, {
|
|
2086
2193
|
sessionsRoot: node_path_1.default.join(codexHomeDir, 'sessions'),
|
|
2194
|
+
deleteAfterRead: false,
|
|
2087
2195
|
});
|
|
2088
2196
|
if (rolloutUsage.usageParseError || rolloutUsage.totalTokens === null) {
|
|
2089
2197
|
if (result.tokenUsage.usageParseError) {
|
|
2090
2198
|
console.warn(`Codex rollout token usage could not be read for thread ${observedCodexThreadId}: ${rolloutUsage.usageParseError ?? 'unknown_error'}.`);
|
|
2091
2199
|
}
|
|
2092
|
-
return result;
|
|
2200
|
+
return { ...result, providerSessionId: observedCodexThreadId };
|
|
2093
2201
|
}
|
|
2094
2202
|
if (!result.tokenUsage.usageParseError
|
|
2095
2203
|
&& result.tokenUsage.totalTokens !== null
|
|
@@ -2098,20 +2206,25 @@ async function runCodex(input) {
|
|
|
2098
2206
|
}
|
|
2099
2207
|
return {
|
|
2100
2208
|
...result,
|
|
2209
|
+
providerSessionId: observedCodexThreadId,
|
|
2101
2210
|
tokenUsage: rolloutUsage,
|
|
2102
2211
|
tokensUsed: rolloutUsage.totalTokens,
|
|
2103
2212
|
};
|
|
2104
2213
|
}
|
|
2105
2214
|
async function runClaude(input) {
|
|
2106
|
-
const
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2215
|
+
const playdropConfigPath = input.cleanRoom
|
|
2216
|
+
? null
|
|
2217
|
+
: (await prepareClaudeRunConfig({
|
|
2218
|
+
workspaceDir: input.stateDir ?? input.workspaceDir,
|
|
2219
|
+
workerUsername: input.workerUsername,
|
|
2220
|
+
envName: input.envName,
|
|
2221
|
+
})).playdropConfigPath;
|
|
2111
2222
|
const denyReadRoots = resolveClaudeDenyReadRoots(input.workspaceDir);
|
|
2112
2223
|
const screenshotCollector = input.enableChrome || input.enablePlaywrightMcp
|
|
2113
2224
|
? (0, instrument_evidence_1.createWorkerInstrumentScreenshotCollector)(input.workspaceDir)
|
|
2114
2225
|
: null;
|
|
2226
|
+
let observedClaudeSessionId = input.resumeSessionId?.trim() || null;
|
|
2227
|
+
let claudeSessionProbe = '';
|
|
2115
2228
|
let playwrightMcpConfigPath = null;
|
|
2116
2229
|
if (input.enablePlaywrightMcp) {
|
|
2117
2230
|
playwrightMcpConfigPath = node_path_1.default.join(input.workspaceDir, '.playdrop', 'claude-run', 'playwright-mcp.json');
|
|
@@ -2125,12 +2238,16 @@ async function runClaude(input) {
|
|
|
2125
2238
|
workspaceDir: input.workspaceDir,
|
|
2126
2239
|
denyReadRoots,
|
|
2127
2240
|
pluginDir: input.taskPluginRoot,
|
|
2241
|
+
playdropCliEnabled: !input.cleanRoom,
|
|
2128
2242
|
chrome: input.enableChrome,
|
|
2129
2243
|
playwrightMcp: input.enablePlaywrightMcp,
|
|
2130
2244
|
playwrightMcpConfigPath,
|
|
2245
|
+
resumeSessionId: input.resumeSessionId,
|
|
2131
2246
|
}),
|
|
2132
2247
|
cwd: input.workspaceDir,
|
|
2133
|
-
env: (0, runtime_1.
|
|
2248
|
+
env: input.cleanRoom ? (0, runtime_1.buildCleanRoomChildEnv)({
|
|
2249
|
+
binDir: input.binDir,
|
|
2250
|
+
}) : (0, runtime_1.buildWorkerChildEnv)({
|
|
2134
2251
|
binDir: input.binDir,
|
|
2135
2252
|
taskId: input.taskId,
|
|
2136
2253
|
taskToken: input.taskToken,
|
|
@@ -2138,7 +2255,7 @@ async function runClaude(input) {
|
|
|
2138
2255
|
envName: input.envName,
|
|
2139
2256
|
eventDir: input.eventDir,
|
|
2140
2257
|
devPort: input.devPort,
|
|
2141
|
-
playdropConfigPath,
|
|
2258
|
+
playdropConfigPath: playdropConfigPath,
|
|
2142
2259
|
playwrightBrowsersPath: resolveWorkerPlaywrightBrowsersPath(),
|
|
2143
2260
|
}),
|
|
2144
2261
|
stdin: `${JSON.stringify({
|
|
@@ -2158,9 +2275,15 @@ async function runClaude(input) {
|
|
|
2158
2275
|
],
|
|
2159
2276
|
},
|
|
2160
2277
|
})}\n`,
|
|
2161
|
-
timeoutMs: (0, runtime_1.readPositiveEnvInt)('PLAYDROP_WORKER_CLAUDE_TIMEOUT_MS', runtime_1.DEFAULT_CODEX_TIMEOUT_MS),
|
|
2278
|
+
timeoutMs: input.timeoutMs ?? (0, runtime_1.readPositiveEnvInt)('PLAYDROP_WORKER_CLAUDE_TIMEOUT_MS', runtime_1.DEFAULT_CODEX_TIMEOUT_MS),
|
|
2162
2279
|
maxOutputChars: runtime_1.DEFAULT_CODEX_LOG_TAIL_CHARS,
|
|
2163
|
-
onTranscriptChunks:
|
|
2280
|
+
onTranscriptChunks: async (chunks) => {
|
|
2281
|
+
if (!observedClaudeSessionId) {
|
|
2282
|
+
claudeSessionProbe = (claudeSessionProbe + chunks.map((chunk) => chunk.content).join('')).slice(-64000);
|
|
2283
|
+
observedClaudeSessionId = (0, runtime_1.extractClaudeSessionId)(claudeSessionProbe);
|
|
2284
|
+
}
|
|
2285
|
+
await input.onTranscriptChunks(chunks);
|
|
2286
|
+
},
|
|
2164
2287
|
...(screenshotCollector
|
|
2165
2288
|
? { onStdoutText: (text) => screenshotCollector.push(text) > 0 }
|
|
2166
2289
|
: {}),
|
|
@@ -2168,7 +2291,9 @@ async function runClaude(input) {
|
|
|
2168
2291
|
onChild: input.onChild,
|
|
2169
2292
|
});
|
|
2170
2293
|
screenshotCollector?.finish();
|
|
2171
|
-
|
|
2294
|
+
observedClaudeSessionId = observedClaudeSessionId
|
|
2295
|
+
?? (0, runtime_1.extractClaudeSessionId)(`${result.stdout}\n${result.stderr}\n${result.outputTail}`);
|
|
2296
|
+
return { ...result, providerSessionId: observedClaudeSessionId };
|
|
2172
2297
|
}
|
|
2173
2298
|
function isPathInside(parent, child) {
|
|
2174
2299
|
const relative = node_path_1.default.relative(parent, child);
|
|
@@ -2199,6 +2324,35 @@ function resolveClaudeDenyReadRoots(workspaceDir) {
|
|
|
2199
2324
|
}
|
|
2200
2325
|
// Raw stdout/stderr tails stay out of task results on purpose: transcripts are
|
|
2201
2326
|
// streamed separately and results can surface to creators (spec section 11).
|
|
2327
|
+
function mergeCreatorRecoveryResults(runtime, initial, recovery) {
|
|
2328
|
+
const sumNullable = (left, right) => (left === null && right === null ? null : (left ?? 0) + (right ?? 0));
|
|
2329
|
+
const tokenUsage = runtime === 'CODEX' && recovery.tokenUsage.totalTokens !== null
|
|
2330
|
+
? recovery.tokenUsage
|
|
2331
|
+
: {
|
|
2332
|
+
inputTokens: sumNullable(initial.tokenUsage.inputTokens, recovery.tokenUsage.inputTokens),
|
|
2333
|
+
outputTokens: sumNullable(initial.tokenUsage.outputTokens, recovery.tokenUsage.outputTokens),
|
|
2334
|
+
cacheCreationInputTokens: sumNullable(initial.tokenUsage.cacheCreationInputTokens, recovery.tokenUsage.cacheCreationInputTokens),
|
|
2335
|
+
cacheReadInputTokens: sumNullable(initial.tokenUsage.cacheReadInputTokens, recovery.tokenUsage.cacheReadInputTokens),
|
|
2336
|
+
totalTokens: sumNullable(initial.tokenUsage.totalTokens, recovery.tokenUsage.totalTokens),
|
|
2337
|
+
rawProviderUsage: {
|
|
2338
|
+
initial: initial.tokenUsage.rawProviderUsage,
|
|
2339
|
+
recovery: recovery.tokenUsage.rawProviderUsage,
|
|
2340
|
+
},
|
|
2341
|
+
usageParseError: initial.tokenUsage.totalTokens === null || recovery.tokenUsage.totalTokens === null
|
|
2342
|
+
? 'creator_recovery_usage_incomplete'
|
|
2343
|
+
: null,
|
|
2344
|
+
};
|
|
2345
|
+
const recoveryMarker = recovery.timedOut ? '\nagent_task_creation_recovery_timeout' : '';
|
|
2346
|
+
return {
|
|
2347
|
+
...recovery,
|
|
2348
|
+
stdout: `${initial.stdout}\n${recovery.stdout}`.slice(-runtime_1.DEFAULT_CODEX_LOG_TAIL_CHARS),
|
|
2349
|
+
stderr: `${initial.stderr}\n${recovery.stderr}${recoveryMarker}`.slice(-runtime_1.DEFAULT_CODEX_LOG_TAIL_CHARS),
|
|
2350
|
+
outputTail: `${initial.outputTail}\n${recovery.outputTail}${recoveryMarker}`.slice(-runtime_1.DEFAULT_CODEX_LOG_TAIL_CHARS),
|
|
2351
|
+
tokenUsage,
|
|
2352
|
+
tokensUsed: tokenUsage.totalTokens,
|
|
2353
|
+
providerSessionId: recovery.providerSessionId ?? initial.providerSessionId ?? null,
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2202
2356
|
function buildAgentRunResult(result) {
|
|
2203
2357
|
return {
|
|
2204
2358
|
exitCode: result.exitCode,
|
|
@@ -2326,6 +2480,12 @@ function extractProviderStatusCode(text) {
|
|
|
2326
2480
|
function buildAgentFailureCode(agent, result, request) {
|
|
2327
2481
|
const provider = providerNameForAgent(agent);
|
|
2328
2482
|
const combinedOutput = `${result.outputTail}\n${result.stderr}\n${result.stdout}`;
|
|
2483
|
+
if (combinedOutput.includes('agent_task_resume_session_missing')) {
|
|
2484
|
+
return `agent_task_resume_session_missing:${provider}`;
|
|
2485
|
+
}
|
|
2486
|
+
if (combinedOutput.includes('agent_task_creation_recovery_timeout')) {
|
|
2487
|
+
return `agent_task_creation_recovery_timeout:${provider}`;
|
|
2488
|
+
}
|
|
2329
2489
|
if (request
|
|
2330
2490
|
&& (/\b(?:unknown|invalid|unsupported)\s+(?:model|effort)\b/i.test(combinedOutput)
|
|
2331
2491
|
|| /\b(?:model|effort)\b.{0,80}\b(?:not found|not available|does not exist|is unavailable|is not supported)\b/i.test(combinedOutput))) {
|
|
@@ -2363,6 +2523,12 @@ function describeAgentFailureForEvent(errorCode) {
|
|
|
2363
2523
|
if (code === 'agent_provider_timeout') {
|
|
2364
2524
|
return `${providerLabel} timed out before completing the task. Retry this task when the provider is healthy.`;
|
|
2365
2525
|
}
|
|
2526
|
+
if (code === 'agent_task_resume_session_missing') {
|
|
2527
|
+
return `${providerLabel} timed out, but the worker could not recover the same conversation. The task was stopped without starting over.`;
|
|
2528
|
+
}
|
|
2529
|
+
if (code === 'agent_task_creation_recovery_timeout') {
|
|
2530
|
+
return `${providerLabel} did not finish during the final creation recovery window, so the task was stopped permanently.`;
|
|
2531
|
+
}
|
|
2366
2532
|
if (code === 'worker_shutdown') {
|
|
2367
2533
|
return 'The worker stopped while running this task. Start the worker again, then retry the task.';
|
|
2368
2534
|
}
|
|
@@ -2422,6 +2588,24 @@ async function createLocalPlaydropShim(input) {
|
|
|
2422
2588
|
}
|
|
2423
2589
|
return binDir;
|
|
2424
2590
|
}
|
|
2591
|
+
async function createStaticGameCleanToolPath(workspaceDir, runtime) {
|
|
2592
|
+
const binDir = node_path_1.default.join(workspaceDir, '.clean-tools');
|
|
2593
|
+
await (0, promises_1.rm)(binDir, { recursive: true, force: true });
|
|
2594
|
+
await (0, promises_1.mkdir)(binDir, { recursive: true });
|
|
2595
|
+
const commands = runtime === 'CODEX'
|
|
2596
|
+
? ['codex', 'node', 'npm', 'npx']
|
|
2597
|
+
: ['claude', 'node', 'npm', 'npx'];
|
|
2598
|
+
for (const command of commands) {
|
|
2599
|
+
const target = command === 'node'
|
|
2600
|
+
? node_process_1.default.execPath
|
|
2601
|
+
: (await execFileAsync('/usr/bin/which', [command])).stdout.trim();
|
|
2602
|
+
if (!target || !node_path_1.default.isAbsolute(target)) {
|
|
2603
|
+
throw new Error(`static_game_clean_tool_missing:${command}`);
|
|
2604
|
+
}
|
|
2605
|
+
(0, node_fs_1.symlinkSync)(target, node_path_1.default.join(binDir, command));
|
|
2606
|
+
}
|
|
2607
|
+
return binDir;
|
|
2608
|
+
}
|
|
2425
2609
|
function probeCodexInstallation() {
|
|
2426
2610
|
const probe = (0, shellProbe_1.runShell)((0, shellProbe_1.buildCommandPathProbe)('codex'));
|
|
2427
2611
|
if (probe.exitCode !== 0 || !probe.output.trim()) {
|
|
@@ -2643,6 +2827,111 @@ function errorMessage(error) {
|
|
|
2643
2827
|
function errorCode(error) {
|
|
2644
2828
|
return errorMessage(error).split(':')[0]?.trim() || 'unknown_error';
|
|
2645
2829
|
}
|
|
2830
|
+
function workerCommandExists(command) {
|
|
2831
|
+
const probe = (0, shellProbe_1.runShell)((0, shellProbe_1.buildCommandPathProbe)(command));
|
|
2832
|
+
return probe.exitCode === 0 && Boolean(probe.output.trim());
|
|
2833
|
+
}
|
|
2834
|
+
function resolveInstalledChromeExecutable() {
|
|
2835
|
+
if (node_process_1.default.platform === 'darwin') {
|
|
2836
|
+
const executable = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
2837
|
+
return (0, node_fs_1.existsSync)(executable) ? executable : null;
|
|
2838
|
+
}
|
|
2839
|
+
if (node_process_1.default.platform === 'win32') {
|
|
2840
|
+
const roots = [node_process_1.default.env.PROGRAMFILES, node_process_1.default.env['PROGRAMFILES(X86)'], node_process_1.default.env.LOCALAPPDATA]
|
|
2841
|
+
.map((value) => value?.trim())
|
|
2842
|
+
.filter((value) => Boolean(value));
|
|
2843
|
+
for (const root of roots) {
|
|
2844
|
+
const executable = node_path_1.default.join(root, 'Google', 'Chrome', 'Application', 'chrome.exe');
|
|
2845
|
+
if ((0, node_fs_1.existsSync)(executable)) {
|
|
2846
|
+
return executable;
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
return null;
|
|
2850
|
+
}
|
|
2851
|
+
return workerCommandExists('google-chrome') ? 'google-chrome' : null;
|
|
2852
|
+
}
|
|
2853
|
+
function collectWorkerInfrastructureFailures() {
|
|
2854
|
+
const failures = [];
|
|
2855
|
+
if (!resolveInstalledChromeExecutable()) {
|
|
2856
|
+
failures.push('worker_preflight_google_chrome_missing');
|
|
2857
|
+
}
|
|
2858
|
+
try {
|
|
2859
|
+
(0, runtime_1.resolvePlaywrightMcpCliPath)();
|
|
2860
|
+
}
|
|
2861
|
+
catch (error) {
|
|
2862
|
+
failures.push(errorCode(error));
|
|
2863
|
+
}
|
|
2864
|
+
if (node_process_1.default.platform !== 'darwin') {
|
|
2865
|
+
return failures;
|
|
2866
|
+
}
|
|
2867
|
+
if (node_process_1.default.arch !== recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.architecture) {
|
|
2868
|
+
failures.push('worker_preflight_recorder_architecture_unsupported');
|
|
2869
|
+
return failures;
|
|
2870
|
+
}
|
|
2871
|
+
if (!workerCommandExists('ffmpeg')) {
|
|
2872
|
+
failures.push('worker_preflight_ffmpeg_missing');
|
|
2873
|
+
}
|
|
2874
|
+
if (!workerCommandExists('ffprobe')) {
|
|
2875
|
+
failures.push('worker_preflight_ffprobe_missing');
|
|
2876
|
+
}
|
|
2877
|
+
const guiSession = (0, shellProbe_1.runShell)('/bin/launchctl print "gui/$(/usr/bin/id -u)"');
|
|
2878
|
+
if (guiSession.exitCode !== 0) {
|
|
2879
|
+
failures.push('worker_preflight_gui_session_missing');
|
|
2880
|
+
}
|
|
2881
|
+
const recorderPath = (0, recorderRelease_1.resolveMacWorkerRecorderPath)();
|
|
2882
|
+
if (!(0, node_fs_1.existsSync)(recorderPath)) {
|
|
2883
|
+
failures.push('worker_preflight_recorder_missing');
|
|
2884
|
+
return failures;
|
|
2885
|
+
}
|
|
2886
|
+
const recorderStat = (0, node_fs_1.statSync)(recorderPath);
|
|
2887
|
+
if (!recorderStat.isFile() || (recorderStat.mode & 0o111) === 0) {
|
|
2888
|
+
failures.push('worker_preflight_recorder_not_executable');
|
|
2889
|
+
return failures;
|
|
2890
|
+
}
|
|
2891
|
+
const installedSha256 = (0, recorderRelease_1.recorderSha256)((0, node_fs_1.readFileSync)(recorderPath));
|
|
2892
|
+
if (installedSha256 !== recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.sha256) {
|
|
2893
|
+
failures.push('worker_preflight_recorder_hash_mismatch');
|
|
2894
|
+
return failures;
|
|
2895
|
+
}
|
|
2896
|
+
const permission = (0, node_child_process_1.spawnSync)(recorderPath, ['--check-permission'], {
|
|
2897
|
+
encoding: 'utf8',
|
|
2898
|
+
timeout: 10000,
|
|
2899
|
+
});
|
|
2900
|
+
if (permission.status !== 0) {
|
|
2901
|
+
failures.push('worker_preflight_screen_recording_permission_missing');
|
|
2902
|
+
}
|
|
2903
|
+
return failures;
|
|
2904
|
+
}
|
|
2905
|
+
function describeWorkerPreflightReason(reason) {
|
|
2906
|
+
if (reason.startsWith('worker_preflight_recorder_download_failed:')) {
|
|
2907
|
+
return `The PlayDrop macOS recorder could not be downloaded: ${reason.slice('worker_preflight_recorder_download_failed:'.length).trim()}`;
|
|
2908
|
+
}
|
|
2909
|
+
const descriptions = {
|
|
2910
|
+
worker_preflight_google_chrome_missing: 'Google Chrome is missing from its standard installation path.',
|
|
2911
|
+
worker_preflight_playwright_missing: 'The PlayDrop CLI cannot load Playwright.',
|
|
2912
|
+
worker_preflight_chromium_missing: 'The Chromium executable required by Playwright is missing.',
|
|
2913
|
+
worker_preflight_playwright_mcp_missing: 'The pinned Playwright MCP package is missing from the PlayDrop CLI installation.',
|
|
2914
|
+
worker_preflight_playwright_mcp_version: 'The installed Playwright MCP package does not match the PlayDrop CLI release.',
|
|
2915
|
+
worker_preflight_ffmpeg_missing: 'ffmpeg is missing from PATH.',
|
|
2916
|
+
worker_preflight_ffprobe_missing: 'ffprobe is missing from PATH.',
|
|
2917
|
+
worker_preflight_gui_session_missing: 'No active macOS GUI session is available.',
|
|
2918
|
+
worker_preflight_recorder_architecture_unsupported: 'The native recorder is currently available only for Apple silicon Macs.',
|
|
2919
|
+
worker_preflight_recorder_missing: 'The PlayDrop macOS recorder is missing.',
|
|
2920
|
+
worker_preflight_recorder_not_executable: 'The PlayDrop macOS recorder is not executable.',
|
|
2921
|
+
worker_preflight_recorder_hash_mismatch: 'The PlayDrop macOS recorder does not match this CLI release.',
|
|
2922
|
+
worker_preflight_screen_recording_permission_missing: 'Screen Recording permission is not granted to the PlayDrop recorder.',
|
|
2923
|
+
codex_not_authenticated: 'Codex is installed but not authenticated.',
|
|
2924
|
+
codex_models_unavailable: 'Codex is authenticated but reported no usable models.',
|
|
2925
|
+
claude_not_authenticated: 'Claude Code is installed but not authenticated.',
|
|
2926
|
+
claude_models_unavailable: 'Claude Code is authenticated but reported no usable models.',
|
|
2927
|
+
agent_cli_not_found: 'No supported authenticated agent CLI is installed.',
|
|
2928
|
+
};
|
|
2929
|
+
return descriptions[reason] ?? reason;
|
|
2930
|
+
}
|
|
2931
|
+
function formatWorkerPreflightFailure(reasons) {
|
|
2932
|
+
const uniqueReasons = Array.from(new Set(reasons));
|
|
2933
|
+
return new Error(`worker_preflight_failed:\n${uniqueReasons.map((reason) => `- ${describeWorkerPreflightReason(reason)}`).join('\n')}`);
|
|
2934
|
+
}
|
|
2646
2935
|
function readWorkerTargetUpdateFailure() {
|
|
2647
2936
|
const failureFile = node_process_1.default.env.PLAYDROP_WORKER_TARGET_UPDATE_FAILURE_FILE?.trim() ?? '';
|
|
2648
2937
|
if (!failureFile || !(0, node_fs_1.existsSync)(failureFile)) {
|
|
@@ -2668,7 +2957,7 @@ function collectWorkerLocalStatus() {
|
|
|
2668
2957
|
let claudeVersion = null;
|
|
2669
2958
|
let claudeModels = [];
|
|
2670
2959
|
let cursorVersion = null;
|
|
2671
|
-
|
|
2960
|
+
const npmVersion = null;
|
|
2672
2961
|
let playwright = null;
|
|
2673
2962
|
let targetUpdateFailure = null;
|
|
2674
2963
|
try {
|
|
@@ -2691,12 +2980,6 @@ function collectWorkerLocalStatus() {
|
|
|
2691
2980
|
catch (error) {
|
|
2692
2981
|
degradedReasons.push(errorCode(error));
|
|
2693
2982
|
}
|
|
2694
|
-
try {
|
|
2695
|
-
npmVersion = probeNpmVersion();
|
|
2696
|
-
}
|
|
2697
|
-
catch (error) {
|
|
2698
|
-
degradedReasons.push(errorCode(error));
|
|
2699
|
-
}
|
|
2700
2983
|
try {
|
|
2701
2984
|
playwright = probePlaywrightChromium();
|
|
2702
2985
|
if (!playwright.chromiumInstalled) {
|
|
@@ -2706,6 +2989,7 @@ function collectWorkerLocalStatus() {
|
|
|
2706
2989
|
catch (error) {
|
|
2707
2990
|
degradedReasons.push(errorCode(error));
|
|
2708
2991
|
}
|
|
2992
|
+
degradedReasons.push(...collectWorkerInfrastructureFailures());
|
|
2709
2993
|
try {
|
|
2710
2994
|
targetUpdateFailure = readWorkerTargetUpdateFailure();
|
|
2711
2995
|
}
|
|
@@ -2739,7 +3023,8 @@ function collectWorkerLocalStatus() {
|
|
|
2739
3023
|
degradedReasons.push(errorCode(error));
|
|
2740
3024
|
}
|
|
2741
3025
|
const uniqueReasons = Array.from(new Set(degradedReasons));
|
|
2742
|
-
const
|
|
3026
|
+
const infrastructureFailures = degradedReasons.filter((reason) => reason.startsWith('worker_preflight_'));
|
|
3027
|
+
const infrastructureReady = Boolean(playwright?.chromiumInstalled) && infrastructureFailures.length === 0;
|
|
2743
3028
|
const ready = Boolean(capabilities?.ready) && infrastructureReady;
|
|
2744
3029
|
return {
|
|
2745
3030
|
ready,
|
|
@@ -2759,6 +3044,11 @@ function collectWorkerLocalStatus() {
|
|
|
2759
3044
|
function buildWorkerSetupActions(input) {
|
|
2760
3045
|
const reasons = new Set(input.degradedReasons);
|
|
2761
3046
|
const actions = [];
|
|
3047
|
+
const addManualAction = (reason, label) => {
|
|
3048
|
+
if (reasons.has(reason)) {
|
|
3049
|
+
actions.push({ reason, label, message: describeWorkerPreflightReason(reason) });
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
2762
3052
|
if (reasons.has('worker_preflight_chromium_missing')) {
|
|
2763
3053
|
const version = input.playwright?.version && input.playwright.version !== 'unknown'
|
|
2764
3054
|
? `@${input.playwright.version}`
|
|
@@ -2787,6 +3077,17 @@ function buildWorkerSetupActions(input) {
|
|
|
2787
3077
|
message: 'Install and authenticate Codex or Claude Code, then rerun playdrop agents status --json.',
|
|
2788
3078
|
});
|
|
2789
3079
|
}
|
|
3080
|
+
addManualAction('worker_preflight_google_chrome_missing', 'Install Google Chrome');
|
|
3081
|
+
addManualAction('worker_preflight_playwright_mcp_missing', 'Repair the PlayDrop CLI installation');
|
|
3082
|
+
addManualAction('worker_preflight_playwright_mcp_version', 'Repair the PlayDrop CLI installation');
|
|
3083
|
+
addManualAction('worker_preflight_ffmpeg_missing', 'Install ffmpeg');
|
|
3084
|
+
addManualAction('worker_preflight_ffprobe_missing', 'Install ffprobe');
|
|
3085
|
+
addManualAction('worker_preflight_gui_session_missing', 'Sign in to the Mac desktop');
|
|
3086
|
+
addManualAction('worker_preflight_recorder_architecture_unsupported', 'Use an Apple silicon Mac');
|
|
3087
|
+
addManualAction('worker_preflight_recorder_missing', 'Restart the worker to download the recorder');
|
|
3088
|
+
addManualAction('worker_preflight_recorder_not_executable', 'Restart the worker to repair the recorder');
|
|
3089
|
+
addManualAction('worker_preflight_recorder_hash_mismatch', 'Restart the worker to repair the recorder');
|
|
3090
|
+
addManualAction('worker_preflight_screen_recording_permission_missing', 'Grant Screen Recording permission');
|
|
2790
3091
|
return actions;
|
|
2791
3092
|
}
|
|
2792
3093
|
function resolveWorkerStateDir() {
|
|
@@ -3609,7 +3910,7 @@ async function setupWorker(options = {}) {
|
|
|
3609
3910
|
const workerName = explicitWorkerName || `${username} - ${node_os_1.default.hostname()}`;
|
|
3610
3911
|
const local = collectWorkerLocalStatus();
|
|
3611
3912
|
const agentReady = local.agents.some((agent) => agent.ready === true);
|
|
3612
|
-
const baseRuntimeReady = Boolean(agentReady && local.
|
|
3913
|
+
const baseRuntimeReady = Boolean(agentReady && local.playwright?.chromiumInstalled);
|
|
3613
3914
|
if (!baseRuntimeReady) {
|
|
3614
3915
|
throw new Error(`worker_setup_preflight_failed: ${local.degradedReasons.join(', ') || 'local worker runtime is not ready'}`);
|
|
3615
3916
|
}
|
|
@@ -3799,7 +4100,7 @@ on run matchers
|
|
|
3799
4100
|
return "remaining=" & remainingCount & ";blank=" & blankWindowCount
|
|
3800
4101
|
end run
|
|
3801
4102
|
`;
|
|
3802
|
-
async function
|
|
4103
|
+
async function enforceInstrumentBrowserHygieneUnlocked(assignment, options = {}) {
|
|
3803
4104
|
if (assignment.agent.browser === 'NONE') {
|
|
3804
4105
|
return { remainingTaskTabs: 0, blankWindows: 0 };
|
|
3805
4106
|
}
|
|
@@ -3814,13 +4115,22 @@ async function enforceInstrumentBrowserHygiene(assignment, options = {}) {
|
|
|
3814
4115
|
const result = await execFileAsync(command, args, { timeout: 15000, maxBuffer: 1024 * 1024 });
|
|
3815
4116
|
return { stdout: result.stdout, stderr: result.stderr };
|
|
3816
4117
|
});
|
|
3817
|
-
let result;
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
4118
|
+
let result = null;
|
|
4119
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
4120
|
+
try {
|
|
4121
|
+
result = await runner('osascript', ['-e', INSTRUMENT_BROWSER_HYGIENE_APPLESCRIPT, ...matchers]);
|
|
4122
|
+
break;
|
|
4123
|
+
}
|
|
4124
|
+
catch (error) {
|
|
4125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4126
|
+
if (!message.includes('Connection is invalid. (-609)') || attempt === 3) {
|
|
4127
|
+
throw new Error(`worker_browser_hygiene_automation_failed:${message}`);
|
|
4128
|
+
}
|
|
4129
|
+
await sleep(250);
|
|
4130
|
+
}
|
|
3823
4131
|
}
|
|
4132
|
+
if (!result)
|
|
4133
|
+
throw new Error('worker_browser_hygiene_automation_failed:no_result');
|
|
3824
4134
|
const match = /remaining=(\d+);blank=(\d+)/.exec(result.stdout.trim());
|
|
3825
4135
|
if (!match) {
|
|
3826
4136
|
throw new Error(`worker_browser_hygiene_invalid_result:${result.stdout.trim() || result.stderr.trim() || 'empty'}`);
|
|
@@ -3832,12 +4142,25 @@ async function enforceInstrumentBrowserHygiene(assignment, options = {}) {
|
|
|
3832
4142
|
}
|
|
3833
4143
|
return { remainingTaskTabs, blankWindows };
|
|
3834
4144
|
}
|
|
4145
|
+
let instrumentBrowserHygieneTail = Promise.resolve();
|
|
4146
|
+
async function enforceInstrumentBrowserHygiene(assignment, options = {}) {
|
|
4147
|
+
const result = instrumentBrowserHygieneTail.then(() => enforceInstrumentBrowserHygieneUnlocked(assignment, options));
|
|
4148
|
+
instrumentBrowserHygieneTail = result.then(() => undefined, () => undefined);
|
|
4149
|
+
return result;
|
|
4150
|
+
}
|
|
4151
|
+
function assignmentRequiresRegisteredBundle(assignment) {
|
|
4152
|
+
const playdropMetadata = assignment.task.metadata.playdrop;
|
|
4153
|
+
return Boolean(playdropMetadata
|
|
4154
|
+
&& typeof playdropMetadata === 'object'
|
|
4155
|
+
&& !Array.isArray(playdropMetadata)
|
|
4156
|
+
&& playdropMetadata.bundleSource === 'REGISTERED');
|
|
4157
|
+
}
|
|
3835
4158
|
function throwWorkerRuntimeError(error) {
|
|
3836
4159
|
throw error;
|
|
3837
4160
|
}
|
|
3838
4161
|
async function startWorker(options = {}) {
|
|
3839
4162
|
loadWorkerEnvFile(options.env);
|
|
3840
|
-
await (0, commandContext_1.withEnvironment)('worker start', 'Starting the PlayDrop worker', async ({ client, env }) => {
|
|
4163
|
+
await (0, commandContext_1.withEnvironment)('worker start', 'Starting the PlayDrop worker', async ({ client, env, envConfig, token }) => {
|
|
3841
4164
|
const devPluginWorkingTree = resolveDevPluginWorkingTree({
|
|
3842
4165
|
workingTree: options.devPluginWorkingTree,
|
|
3843
4166
|
env,
|
|
@@ -3870,15 +4193,35 @@ async function startWorker(options = {}) {
|
|
|
3870
4193
|
const supervisorLock = acquireWorkerSupervisorLock({ supervisorType, workerName });
|
|
3871
4194
|
try {
|
|
3872
4195
|
await removeStaleAgentRunCredentials();
|
|
3873
|
-
const
|
|
3874
|
-
|
|
4196
|
+
const startupFailures = [];
|
|
4197
|
+
if (node_process_1.default.platform === 'darwin') {
|
|
4198
|
+
try {
|
|
4199
|
+
const recorder = await (0, recorderRelease_1.ensureMacWorkerRecorderInstalled)();
|
|
4200
|
+
if (recorder.downloaded) {
|
|
4201
|
+
console.log(`Installed PlayDrop macOS recorder ${recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.version} at ${recorder.path}.`);
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
catch (error) {
|
|
4205
|
+
startupFailures.push(`worker_preflight_recorder_download_failed:${errorMessage(error)}`);
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
else if (node_process_1.default.platform === 'win32') {
|
|
4209
|
+
console.warn('Native listing recording is not available on Windows yet.');
|
|
4210
|
+
}
|
|
4211
|
+
const local = collectWorkerLocalStatus();
|
|
4212
|
+
startupFailures.push(...local.degradedReasons);
|
|
4213
|
+
if (startupFailures.length > 0 || !local.ready || !local.capabilities || !local.playwright?.chromiumInstalled) {
|
|
4214
|
+
throw formatWorkerPreflightFailure(startupFailures);
|
|
4215
|
+
}
|
|
4216
|
+
const npmVersion = local.npmVersion;
|
|
4217
|
+
const playwright = {
|
|
4218
|
+
version: local.playwright.version ?? 'unknown',
|
|
4219
|
+
chromiumInstalled: true,
|
|
4220
|
+
...(local.playwright.executablePath ? { executablePath: local.playwright.executablePath } : {}),
|
|
4221
|
+
};
|
|
3875
4222
|
const workerKey = (0, config_1.getOrCreateWorkerKey)();
|
|
3876
4223
|
const maxParallelTasks = (0, runtime_1.readPositiveEnvInt)('PLAYDROP_WORKER_MAX_PARALLEL_TASKS', DEFAULT_WORKER_MAX_PARALLEL_TASKS);
|
|
3877
|
-
let capabilities =
|
|
3878
|
-
npmVersion,
|
|
3879
|
-
playwright,
|
|
3880
|
-
maxParallelTasks,
|
|
3881
|
-
});
|
|
4224
|
+
let capabilities = local.capabilities;
|
|
3882
4225
|
let lastCapabilityRefreshAt = Date.now();
|
|
3883
4226
|
writeWorkerRuntimeState({
|
|
3884
4227
|
env,
|
|
@@ -4033,6 +4376,7 @@ async function startWorker(options = {}) {
|
|
|
4033
4376
|
let heartbeatObservedFinalizedTask = false;
|
|
4034
4377
|
let retainWorkspace = false;
|
|
4035
4378
|
let workspaceDir = null;
|
|
4379
|
+
let agentWorkspaceDir = null;
|
|
4036
4380
|
let availableSkillPaths = [];
|
|
4037
4381
|
const observedSkillPaths = new Set();
|
|
4038
4382
|
const assignment = claimedAssignment;
|
|
@@ -4062,6 +4406,38 @@ async function startWorker(options = {}) {
|
|
|
4062
4406
|
});
|
|
4063
4407
|
telemetryReported = true;
|
|
4064
4408
|
};
|
|
4409
|
+
const failUploadedArtifactWithoutTaskDone = async () => {
|
|
4410
|
+
const failureCode = 'agent_exited_after_upload_without_task_done';
|
|
4411
|
+
retainWorkspace = true;
|
|
4412
|
+
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4413
|
+
workerKey,
|
|
4414
|
+
leaseToken,
|
|
4415
|
+
kind: 'progress',
|
|
4416
|
+
phase: 'failed',
|
|
4417
|
+
message: 'The agent uploaded an artifact but exited without marking the task done.',
|
|
4418
|
+
pct: null,
|
|
4419
|
+
payload: { error: failureCode },
|
|
4420
|
+
});
|
|
4421
|
+
await failTaskWithRetry(client, task.id, {
|
|
4422
|
+
workerKey,
|
|
4423
|
+
leaseToken,
|
|
4424
|
+
error: failureCode,
|
|
4425
|
+
terminalReason: {
|
|
4426
|
+
kind: 'failure',
|
|
4427
|
+
category: 'agent_failure',
|
|
4428
|
+
code: failureCode,
|
|
4429
|
+
message: 'The agent uploaded an artifact but did not complete the task protocol.',
|
|
4430
|
+
retryable: false,
|
|
4431
|
+
rawError: failureCode,
|
|
4432
|
+
source: 'agent',
|
|
4433
|
+
},
|
|
4434
|
+
result: {
|
|
4435
|
+
stage: 'completion',
|
|
4436
|
+
uploadedArtifactPreserved: true,
|
|
4437
|
+
},
|
|
4438
|
+
});
|
|
4439
|
+
await reportTelemetry('FAILED');
|
|
4440
|
+
};
|
|
4065
4441
|
try {
|
|
4066
4442
|
const codexModel = assignment.agent.runtime === 'CODEX'
|
|
4067
4443
|
? resolveCodexModel(assignment.agent.model, assignment.agent.reasoningEffort)
|
|
@@ -4071,24 +4447,35 @@ async function startWorker(options = {}) {
|
|
|
4071
4447
|
: null;
|
|
4072
4448
|
resolvedRuntimeModel = codexModel?.model ?? claudeModel?.model ?? assignment.agent.model;
|
|
4073
4449
|
reasoningEffort = codexModel?.reasoningEffort ?? claudeModel?.effort ?? null;
|
|
4450
|
+
const cleanRoom = task.kind === 'STATIC_GAME';
|
|
4074
4451
|
workspaceDir = workerTaskWorkspaceDirFromAssignment(assignment);
|
|
4075
|
-
await
|
|
4076
|
-
await (0, promises_1.rm)(workspaceDir, { recursive: true, force: true });
|
|
4077
|
-
await (0, promises_1.mkdir)(workspaceDir, { recursive: true });
|
|
4078
|
-
const eventDir = node_path_1.default.join(workspaceDir, '.playdrop', 'task-events');
|
|
4079
|
-
await (0, promises_1.mkdir)(eventDir, { recursive: true });
|
|
4080
|
-
await stageWorkerTaskContext({
|
|
4081
|
-
workspaceDir,
|
|
4082
|
-
env,
|
|
4083
|
-
devPort,
|
|
4084
|
-
taskContext,
|
|
4085
|
-
});
|
|
4086
|
-
const binDir = await createLocalPlaydropShim({
|
|
4452
|
+
const preparedWorkspace = await prepareClaimedTaskWorkspace({
|
|
4087
4453
|
workspaceDir,
|
|
4088
|
-
|
|
4089
|
-
attempt: taskContext.attempt,
|
|
4090
|
-
devPort,
|
|
4454
|
+
attempt: task.attempts,
|
|
4091
4455
|
});
|
|
4456
|
+
if (preparedWorkspace.resumed) {
|
|
4457
|
+
console.log(`Agent task ${task.id} retry resumed retained workspace attempt ${task.attempts}.`);
|
|
4458
|
+
}
|
|
4459
|
+
agentWorkspaceDir = cleanRoom ? node_path_1.default.join(workspaceDir, 'project') : workspaceDir;
|
|
4460
|
+
await (0, promises_1.mkdir)(agentWorkspaceDir, { recursive: true });
|
|
4461
|
+
const eventDir = node_path_1.default.join(workspaceDir, '.playdrop', 'task-events');
|
|
4462
|
+
await (0, promises_1.mkdir)(eventDir, { recursive: true });
|
|
4463
|
+
if (!cleanRoom) {
|
|
4464
|
+
await stageWorkerTaskContext({
|
|
4465
|
+
workspaceDir,
|
|
4466
|
+
env,
|
|
4467
|
+
devPort,
|
|
4468
|
+
taskContext,
|
|
4469
|
+
});
|
|
4470
|
+
}
|
|
4471
|
+
const binDir = cleanRoom
|
|
4472
|
+
? await createStaticGameCleanToolPath(workspaceDir, assignment.agent.runtime)
|
|
4473
|
+
: await createLocalPlaydropShim({
|
|
4474
|
+
workspaceDir,
|
|
4475
|
+
taskId: taskContext.taskId,
|
|
4476
|
+
attempt: taskContext.attempt,
|
|
4477
|
+
devPort,
|
|
4478
|
+
});
|
|
4092
4479
|
const handleEventDrainFailure = (error) => {
|
|
4093
4480
|
const classification = classifyWorkerEventDrainError(error);
|
|
4094
4481
|
if (classification === 'session_expired') {
|
|
@@ -4120,17 +4507,59 @@ async function startWorker(options = {}) {
|
|
|
4120
4507
|
appendObservedSkillPathsFromTranscript(chunks);
|
|
4121
4508
|
await appendWorkerTranscriptChunks({ client, taskId: task.id, workerKey, leaseToken, chunks });
|
|
4122
4509
|
};
|
|
4123
|
-
await stageAssignmentWorkspaceV2(
|
|
4124
|
-
const assignmentPluginRoot =
|
|
4125
|
-
|
|
4510
|
+
await stageAssignmentWorkspaceV2(agentWorkspaceDir, assignment.workspace);
|
|
4511
|
+
const assignmentPluginRoot = !cleanRoom
|
|
4512
|
+
&& devPluginWorkingTree
|
|
4513
|
+
&& !assignmentRequiresRegisteredBundle(assignment)
|
|
4514
|
+
? await stageDevPluginWorkingTree({
|
|
4515
|
+
workingTree: devPluginWorkingTree,
|
|
4516
|
+
workspaceDir: agentWorkspaceDir,
|
|
4517
|
+
taskKind: task.kind,
|
|
4518
|
+
})
|
|
4126
4519
|
: assignment.plugin
|
|
4127
4520
|
? await stageAssignmentPluginBundle({
|
|
4128
|
-
workspaceDir,
|
|
4521
|
+
workspaceDir: agentWorkspaceDir,
|
|
4522
|
+
extractionDir: workspaceDir,
|
|
4129
4523
|
pluginBundle: assignment.plugin,
|
|
4130
4524
|
taskKind: task.kind,
|
|
4131
4525
|
})
|
|
4132
4526
|
: null;
|
|
4133
4527
|
availableSkillPaths = listPluginSkillPaths(assignmentPluginRoot);
|
|
4528
|
+
if (cleanRoom) {
|
|
4529
|
+
const playdropMetadata = assignment.task.metadata.playdrop;
|
|
4530
|
+
const postAgentAction = playdropMetadata && typeof playdropMetadata === 'object' && !Array.isArray(playdropMetadata)
|
|
4531
|
+
? playdropMetadata.postAgentAction
|
|
4532
|
+
: null;
|
|
4533
|
+
const publishInstruction = postAgentAction && typeof postAgentAction === 'object' && !Array.isArray(postAgentAction)
|
|
4534
|
+
? postAgentAction
|
|
4535
|
+
: null;
|
|
4536
|
+
if (!assignment.plugin
|
|
4537
|
+
|| assignmentPluginRoot !== null
|
|
4538
|
+
|| assignment.agent.browser !== 'NONE'
|
|
4539
|
+
|| publishInstruction?.type !== 'PUBLISH_STATIC_GAME'
|
|
4540
|
+
|| publishInstruction.entryPoint !== 'index.html') {
|
|
4541
|
+
throw new Error('static_game_clean_room_contract_invalid');
|
|
4542
|
+
}
|
|
4543
|
+
availableSkillPaths = [];
|
|
4544
|
+
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4545
|
+
workerKey,
|
|
4546
|
+
leaseToken,
|
|
4547
|
+
kind: 'system',
|
|
4548
|
+
phase: 'clean_room',
|
|
4549
|
+
message: 'Static game clean-room capability evidence recorded.',
|
|
4550
|
+
payload: {
|
|
4551
|
+
bundleHash: assignment.plugin.sha256,
|
|
4552
|
+
requestedModel: assignment.agent.model,
|
|
4553
|
+
resolvedModel: resolvedRuntimeModel,
|
|
4554
|
+
networkAccess: true,
|
|
4555
|
+
browser: assignment.agent.browser,
|
|
4556
|
+
availableSkills: [],
|
|
4557
|
+
pluginInstalled: false,
|
|
4558
|
+
playdropCliInPath: false,
|
|
4559
|
+
taskCredentialsExposed: false,
|
|
4560
|
+
},
|
|
4561
|
+
});
|
|
4562
|
+
}
|
|
4134
4563
|
heartbeatTimer = setInterval(() => {
|
|
4135
4564
|
client.workerHeartbeatAgentTask(task.id, { workerKey, leaseToken })
|
|
4136
4565
|
.then((heartbeat) => {
|
|
@@ -4189,81 +4618,161 @@ async function startWorker(options = {}) {
|
|
|
4189
4618
|
handleEventDrainFailure(error);
|
|
4190
4619
|
});
|
|
4191
4620
|
}, 1000);
|
|
4192
|
-
const
|
|
4621
|
+
const gameCreationTask = usesCreatorRecovery(task.kind);
|
|
4622
|
+
const startsInCreationRecovery = gameCreationTask && preparedWorkspace.resumed;
|
|
4623
|
+
const prompt = startsInCreationRecovery
|
|
4624
|
+
? buildCreatorRecoveryPrompt({
|
|
4625
|
+
prompt: assignment.request.prompt,
|
|
4626
|
+
kind: task.kind,
|
|
4627
|
+
})
|
|
4628
|
+
: task.kind === 'GAME_EVAL' && preparedWorkspace.resumed
|
|
4629
|
+
? buildGameEvalRetryPrompt(assignment.request.prompt)
|
|
4630
|
+
: assignment.request.prompt;
|
|
4193
4631
|
const imageAttachments = assignment.request.attachments
|
|
4194
4632
|
.filter((attachment) => attachment.contentType.startsWith('image/'))
|
|
4195
4633
|
.map((attachment) => ({
|
|
4196
|
-
path: resolveWorkspaceFileDestination(
|
|
4634
|
+
path: resolveWorkspaceFileDestination(agentWorkspaceDir, attachment.path),
|
|
4197
4635
|
contentType: attachment.contentType,
|
|
4198
4636
|
}));
|
|
4199
4637
|
if (shuttingDown) {
|
|
4200
4638
|
throw new Error('worker_shutdown');
|
|
4201
4639
|
}
|
|
4640
|
+
const runAssignedAgent = async (input) => {
|
|
4641
|
+
if (assignment.agent.runtime === 'CODEX') {
|
|
4642
|
+
return runCodex({
|
|
4643
|
+
workspaceDir: agentWorkspaceDir,
|
|
4644
|
+
stateDir: cleanRoom ? workspaceDir : undefined,
|
|
4645
|
+
binDir,
|
|
4646
|
+
eventDir,
|
|
4647
|
+
prompt: input.prompt,
|
|
4648
|
+
envName: env,
|
|
4649
|
+
workerUsername: username,
|
|
4650
|
+
taskId: task.id,
|
|
4651
|
+
taskToken: assignment.task.token,
|
|
4652
|
+
attempt: taskContext.attempt,
|
|
4653
|
+
devPort,
|
|
4654
|
+
codexModel: codexModel ?? resolveCodexModel(assignment.agent.model),
|
|
4655
|
+
taskPluginRoot: assignmentPluginRoot,
|
|
4656
|
+
enablePlaywrightMcp: assignment.agent.browser === 'PLAYWRIGHT',
|
|
4657
|
+
instrumentSurface: resolveWorkerInstrumentSurface(taskContext),
|
|
4658
|
+
imagePaths: imageAttachments.map((attachment) => attachment.path),
|
|
4659
|
+
onTranscriptChunks: async (chunks) => {
|
|
4660
|
+
await appendTranscriptChunks(chunks);
|
|
4661
|
+
},
|
|
4662
|
+
onChild: (controls) => {
|
|
4663
|
+
activeTerminators.set(task.id, controls.terminate);
|
|
4664
|
+
},
|
|
4665
|
+
cleanRoom,
|
|
4666
|
+
timeoutMs: input.timeoutMs,
|
|
4667
|
+
resumeSessionId: input.resumeSessionId,
|
|
4668
|
+
});
|
|
4669
|
+
}
|
|
4670
|
+
if (assignment.agent.runtime === 'CLAUDE_CODE') {
|
|
4671
|
+
return runClaude({
|
|
4672
|
+
workspaceDir: agentWorkspaceDir,
|
|
4673
|
+
stateDir: cleanRoom ? workspaceDir : undefined,
|
|
4674
|
+
binDir,
|
|
4675
|
+
eventDir,
|
|
4676
|
+
prompt: input.prompt,
|
|
4677
|
+
enableChrome: assignment.agent.browser === 'CHROME',
|
|
4678
|
+
enablePlaywrightMcp: assignment.agent.browser === 'PLAYWRIGHT',
|
|
4679
|
+
instrumentSurface: resolveWorkerInstrumentSurface(taskContext),
|
|
4680
|
+
envName: env,
|
|
4681
|
+
workerUsername: username,
|
|
4682
|
+
taskId: task.id,
|
|
4683
|
+
taskToken: assignment.task.token,
|
|
4684
|
+
attempt: taskContext.attempt,
|
|
4685
|
+
devPort,
|
|
4686
|
+
claudeModel: claudeModel ?? resolveClaudeModel(assignment.agent.model),
|
|
4687
|
+
taskPluginRoot: assignmentPluginRoot,
|
|
4688
|
+
imageAttachments,
|
|
4689
|
+
onTranscriptChunks: async (chunks) => {
|
|
4690
|
+
await appendTranscriptChunks(chunks);
|
|
4691
|
+
},
|
|
4692
|
+
onChild: (controls) => {
|
|
4693
|
+
activeTerminators.set(task.id, controls.terminate);
|
|
4694
|
+
},
|
|
4695
|
+
cleanRoom,
|
|
4696
|
+
timeoutMs: input.timeoutMs,
|
|
4697
|
+
resumeSessionId: input.resumeSessionId,
|
|
4698
|
+
});
|
|
4699
|
+
}
|
|
4700
|
+
if (assignment.agent.runtime === 'CURSOR_COMPOSER') {
|
|
4701
|
+
await client.workerRecordAgentTaskAttemptUnavailable(task.id, {
|
|
4702
|
+
workerKey,
|
|
4703
|
+
leaseToken,
|
|
4704
|
+
attemptIndex: 0,
|
|
4705
|
+
reason: 'unavailable_runtime',
|
|
4706
|
+
message: 'Cursor Composer noninteractive worker execution is not configured on this worker.',
|
|
4707
|
+
});
|
|
4708
|
+
fenced = true;
|
|
4709
|
+
throw new Error('worker_cursor_composer_runner_not_configured');
|
|
4710
|
+
}
|
|
4711
|
+
throw new Error(`unsupported_worker_agent:${assignment.agent.runtime}`);
|
|
4712
|
+
};
|
|
4202
4713
|
agentStartedAt = new Date();
|
|
4203
|
-
if (
|
|
4204
|
-
|
|
4205
|
-
workspaceDir,
|
|
4206
|
-
binDir,
|
|
4207
|
-
eventDir,
|
|
4208
|
-
prompt,
|
|
4209
|
-
envName: env,
|
|
4210
|
-
workerUsername: username,
|
|
4211
|
-
taskId: task.id,
|
|
4212
|
-
taskToken: assignment.task.token,
|
|
4213
|
-
attempt: taskContext.attempt,
|
|
4214
|
-
devPort,
|
|
4215
|
-
codexModel: codexModel ?? resolveCodexModel(assignment.agent.model),
|
|
4216
|
-
taskPluginRoot: assignmentPluginRoot,
|
|
4217
|
-
enablePlaywrightMcp: assignment.agent.browser === 'PLAYWRIGHT',
|
|
4218
|
-
instrumentSurface: resolveWorkerInstrumentSurface(taskContext),
|
|
4219
|
-
imagePaths: imageAttachments.map((attachment) => attachment.path),
|
|
4220
|
-
onTranscriptChunks: async (chunks) => {
|
|
4221
|
-
await appendTranscriptChunks(chunks);
|
|
4222
|
-
},
|
|
4223
|
-
onChild: (controls) => {
|
|
4224
|
-
activeTerminators.set(task.id, controls.terminate);
|
|
4225
|
-
},
|
|
4226
|
-
});
|
|
4227
|
-
}
|
|
4228
|
-
else if (assignment.agent.runtime === 'CLAUDE_CODE') {
|
|
4229
|
-
agentResult = await runClaude({
|
|
4230
|
-
workspaceDir,
|
|
4231
|
-
binDir,
|
|
4232
|
-
eventDir,
|
|
4233
|
-
prompt,
|
|
4234
|
-
enableChrome: assignment.agent.browser === 'CHROME',
|
|
4235
|
-
enablePlaywrightMcp: assignment.agent.browser === 'PLAYWRIGHT',
|
|
4236
|
-
instrumentSurface: resolveWorkerInstrumentSurface(taskContext),
|
|
4237
|
-
envName: env,
|
|
4238
|
-
workerUsername: username,
|
|
4239
|
-
taskId: task.id,
|
|
4240
|
-
taskToken: assignment.task.token,
|
|
4241
|
-
attempt: taskContext.attempt,
|
|
4242
|
-
devPort,
|
|
4243
|
-
claudeModel: claudeModel ?? resolveClaudeModel(assignment.agent.model),
|
|
4244
|
-
taskPluginRoot: assignmentPluginRoot,
|
|
4245
|
-
imageAttachments,
|
|
4246
|
-
onTranscriptChunks: async (chunks) => {
|
|
4247
|
-
await appendTranscriptChunks(chunks);
|
|
4248
|
-
},
|
|
4249
|
-
onChild: (controls) => {
|
|
4250
|
-
activeTerminators.set(task.id, controls.terminate);
|
|
4251
|
-
},
|
|
4252
|
-
});
|
|
4253
|
-
}
|
|
4254
|
-
else if (assignment.agent.runtime === 'CURSOR_COMPOSER') {
|
|
4255
|
-
await client.workerRecordAgentTaskAttemptUnavailable(task.id, {
|
|
4714
|
+
if (startsInCreationRecovery) {
|
|
4715
|
+
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4256
4716
|
workerKey,
|
|
4257
4717
|
leaseToken,
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
message: '
|
|
4718
|
+
kind: 'progress',
|
|
4719
|
+
phase: 'creation_recovery',
|
|
4720
|
+
message: 'Resumed the retained game workspace with a focused creation recovery window.',
|
|
4261
4721
|
});
|
|
4262
|
-
fenced = true;
|
|
4263
|
-
throw new Error('worker_cursor_composer_runner_not_configured');
|
|
4264
4722
|
}
|
|
4265
|
-
|
|
4266
|
-
|
|
4723
|
+
agentResult = await runAssignedAgent({
|
|
4724
|
+
prompt,
|
|
4725
|
+
timeoutMs: startsInCreationRecovery
|
|
4726
|
+
? runtime_1.DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS
|
|
4727
|
+
: gameCreationTask
|
|
4728
|
+
? runtime_1.DEFAULT_CREATOR_AGENT_TIMEOUT_MS
|
|
4729
|
+
: undefined,
|
|
4730
|
+
});
|
|
4731
|
+
if (startsInCreationRecovery && agentResult.timedOut) {
|
|
4732
|
+
const marker = 'agent_task_creation_recovery_timeout';
|
|
4733
|
+
agentResult = {
|
|
4734
|
+
...agentResult,
|
|
4735
|
+
stderr: `${agentResult.stderr}\n${marker}`.trim(),
|
|
4736
|
+
outputTail: `${agentResult.outputTail}\n${marker}`.trim(),
|
|
4737
|
+
};
|
|
4738
|
+
}
|
|
4739
|
+
if (gameCreationTask
|
|
4740
|
+
&& !startsInCreationRecovery
|
|
4741
|
+
&& agentResult.timedOut
|
|
4742
|
+
&& !fenced
|
|
4743
|
+
&& !shuttingDown) {
|
|
4744
|
+
const timedOutResult = agentResult;
|
|
4745
|
+
const refreshedAfterTimeout = await fetchTaskDetail(client, target, task.id);
|
|
4746
|
+
if (refreshedAfterTimeout.task.status === 'RUNNING'
|
|
4747
|
+
&& !hasAgentTaskUploadedArtifact(refreshedAfterTimeout.task)) {
|
|
4748
|
+
const providerSessionId = timedOutResult.providerSessionId?.trim() ?? '';
|
|
4749
|
+
if (!providerSessionId) {
|
|
4750
|
+
const marker = 'agent_task_resume_session_missing';
|
|
4751
|
+
agentResult = {
|
|
4752
|
+
...timedOutResult,
|
|
4753
|
+
stderr: `${timedOutResult.stderr}\n${marker}`.trim(),
|
|
4754
|
+
outputTail: `${timedOutResult.outputTail}\n${marker}`.trim(),
|
|
4755
|
+
};
|
|
4756
|
+
}
|
|
4757
|
+
else {
|
|
4758
|
+
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4759
|
+
workerKey,
|
|
4760
|
+
leaseToken,
|
|
4761
|
+
kind: 'progress',
|
|
4762
|
+
phase: 'creation_recovery',
|
|
4763
|
+
message: 'The one-hour creation ceiling was reached. Resuming the same agent conversation to finish the existing game.',
|
|
4764
|
+
});
|
|
4765
|
+
const recoveryResult = await runAssignedAgent({
|
|
4766
|
+
prompt: buildCreatorRecoveryPrompt({
|
|
4767
|
+
prompt: assignment.request.prompt,
|
|
4768
|
+
kind: task.kind,
|
|
4769
|
+
}),
|
|
4770
|
+
timeoutMs: runtime_1.DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS,
|
|
4771
|
+
resumeSessionId: providerSessionId,
|
|
4772
|
+
});
|
|
4773
|
+
agentResult = mergeCreatorRecoveryResults(assignment.agent.runtime, timedOutResult, recoveryResult);
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4267
4776
|
}
|
|
4268
4777
|
agentCompletedAt = new Date();
|
|
4269
4778
|
await queueEventDrain().catch((error) => {
|
|
@@ -4309,16 +4818,8 @@ async function startWorker(options = {}) {
|
|
|
4309
4818
|
await reportTelemetry(refreshed.task.status);
|
|
4310
4819
|
}
|
|
4311
4820
|
else if (hasAgentTaskUploadedArtifact(refreshed.task)) {
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4315
|
-
workerKey,
|
|
4316
|
-
leaseToken,
|
|
4317
|
-
kind: 'progress',
|
|
4318
|
-
phase: 'upload',
|
|
4319
|
-
message: 'Agent exited after upload before task done; manual completion repair required.',
|
|
4320
|
-
});
|
|
4321
|
-
await reportTelemetry('RUNNING');
|
|
4821
|
+
console.error(`Agent task ${task.id} uploaded an artifact but exited before task completion; failing it explicitly.`);
|
|
4822
|
+
await failUploadedArtifactWithoutTaskDone();
|
|
4322
4823
|
}
|
|
4323
4824
|
else {
|
|
4324
4825
|
retainWorkspace = true;
|
|
@@ -4352,17 +4853,158 @@ async function startWorker(options = {}) {
|
|
|
4352
4853
|
console.error(`Agent task ${task.id} was finalized by the agent as ${refreshed.task.status}.`);
|
|
4353
4854
|
await reportTelemetry(refreshed.task.status);
|
|
4354
4855
|
}
|
|
4355
|
-
else if (
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
leaseToken,
|
|
4361
|
-
kind: 'progress',
|
|
4362
|
-
phase: 'upload',
|
|
4363
|
-
message: 'Agent exited after upload before task done; manual completion repair required.',
|
|
4856
|
+
else if (task.kind === 'STATIC_GAME') {
|
|
4857
|
+
const tokenCap = (0, runtime_1.readPositiveEnvInt)('PLAYDROP_WORKER_TOKEN_CAP', runtime_1.DEFAULT_WORKER_TOKEN_CAP);
|
|
4858
|
+
(0, runtime_1.assertWorkerTokenUsageWithinCap)({
|
|
4859
|
+
tokensUsed: agentResult.tokensUsed,
|
|
4860
|
+
tokenCap,
|
|
4364
4861
|
});
|
|
4365
|
-
await reportTelemetry('
|
|
4862
|
+
await reportTelemetry('DONE');
|
|
4863
|
+
try {
|
|
4864
|
+
let appId = refreshed.task.appId;
|
|
4865
|
+
let appVersionId = refreshed.task.appVersionId;
|
|
4866
|
+
let result;
|
|
4867
|
+
if (Number.isInteger(appId) && Number(appId) > 0
|
|
4868
|
+
&& Number.isInteger(appVersionId) && Number(appVersionId) > 0) {
|
|
4869
|
+
result = {
|
|
4870
|
+
appId,
|
|
4871
|
+
appVersionId,
|
|
4872
|
+
appName: refreshed.task.claimedAppName,
|
|
4873
|
+
version: refreshed.task.outputVersion,
|
|
4874
|
+
completedBy: 'worker_static_game_retry',
|
|
4875
|
+
};
|
|
4876
|
+
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4877
|
+
workerKey,
|
|
4878
|
+
leaseToken,
|
|
4879
|
+
kind: 'system',
|
|
4880
|
+
phase: 'static_game_publish_retry',
|
|
4881
|
+
message: 'Reused the static game version already linked to this task.',
|
|
4882
|
+
});
|
|
4883
|
+
}
|
|
4884
|
+
else {
|
|
4885
|
+
const primarySurface = resolveWorkerInstrumentSurface(taskContext);
|
|
4886
|
+
if (!primarySurface) {
|
|
4887
|
+
throw new Error('static_game_artifact_primary_surface_missing');
|
|
4888
|
+
}
|
|
4889
|
+
if (!agentWorkspaceDir) {
|
|
4890
|
+
throw new Error('static_game_artifact_workspace_missing');
|
|
4891
|
+
}
|
|
4892
|
+
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4893
|
+
workerKey,
|
|
4894
|
+
leaseToken,
|
|
4895
|
+
kind: 'system',
|
|
4896
|
+
phase: 'static_game_publish_started',
|
|
4897
|
+
message: 'Publishing the clean-room static game output.',
|
|
4898
|
+
});
|
|
4899
|
+
const taskClient = (0, apiClient_1.createCliApiClient)({
|
|
4900
|
+
baseUrl: envConfig.apiBase,
|
|
4901
|
+
token,
|
|
4902
|
+
onBehalfCreatorUsername: taskContext.creatorUsername,
|
|
4903
|
+
agentTaskToken: assignment.task.token,
|
|
4904
|
+
agentTaskId: task.id,
|
|
4905
|
+
agentTaskAttempt: taskContext.attempt,
|
|
4906
|
+
});
|
|
4907
|
+
const published = await (0, upload_1.publishStaticHtmlProject)({
|
|
4908
|
+
client: taskClient,
|
|
4909
|
+
taskId: task.id,
|
|
4910
|
+
taskToken: assignment.task.token,
|
|
4911
|
+
projectDir: agentWorkspaceDir,
|
|
4912
|
+
creatorUsername: taskContext.creatorUsername,
|
|
4913
|
+
primarySurface,
|
|
4914
|
+
apiBase: envConfig.apiBase,
|
|
4915
|
+
webBase: envConfig.webBase ?? null,
|
|
4916
|
+
claimedAppName: refreshed.task.claimedAppName,
|
|
4917
|
+
devRouterPort: devPort,
|
|
4918
|
+
token,
|
|
4919
|
+
user: me.user,
|
|
4920
|
+
});
|
|
4921
|
+
appId = published.appId;
|
|
4922
|
+
appVersionId = published.appVersionId;
|
|
4923
|
+
result = {
|
|
4924
|
+
appId,
|
|
4925
|
+
appVersionId,
|
|
4926
|
+
appName: published.appName,
|
|
4927
|
+
version: published.version,
|
|
4928
|
+
versionNodeId: published.versionNodeId,
|
|
4929
|
+
creatorUsername: published.creatorUsername,
|
|
4930
|
+
sourceHash: published.sourceHash,
|
|
4931
|
+
runtimeHash: published.runtimeHash,
|
|
4932
|
+
playUrl: published.playUrl,
|
|
4933
|
+
sourceContainsInjectedSdk: published.sourceContainsInjectedSdk,
|
|
4934
|
+
runtimeContainsInjectedSdk: published.runtimeContainsInjectedSdk,
|
|
4935
|
+
hostedLoadCheckPassed: published.hostedLoadCheckPassed,
|
|
4936
|
+
versionVisibility: 'PRIVATE',
|
|
4937
|
+
warnings: published.warnings,
|
|
4938
|
+
completedBy: 'worker_static_game_publish',
|
|
4939
|
+
};
|
|
4940
|
+
}
|
|
4941
|
+
if (!Number.isInteger(appId) || Number(appId) <= 0 || !Number.isInteger(appVersionId) || Number(appVersionId) <= 0) {
|
|
4942
|
+
throw new Error('static_game_publish_linked_version_missing');
|
|
4943
|
+
}
|
|
4944
|
+
await client.workerCompleteAgentTask(task.id, {
|
|
4945
|
+
workerKey,
|
|
4946
|
+
leaseToken,
|
|
4947
|
+
appId: Number(appId),
|
|
4948
|
+
appVersionId: Number(appVersionId),
|
|
4949
|
+
result,
|
|
4950
|
+
summary: 'Created and privately hosted a standalone static web game.',
|
|
4951
|
+
tokensUsed: agentResult.tokensUsed,
|
|
4952
|
+
});
|
|
4953
|
+
retainWorkspace = false;
|
|
4954
|
+
console.log(`Agent task ${task.id} published and completed as STATIC_GAME.`);
|
|
4955
|
+
}
|
|
4956
|
+
catch (error) {
|
|
4957
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
4958
|
+
const artifactFailure = rawMessage.startsWith('static_game_artifact_');
|
|
4959
|
+
const failureCode = artifactFailure
|
|
4960
|
+
? rawMessage.split(':', 1)[0]
|
|
4961
|
+
: 'static_game_publish_failed';
|
|
4962
|
+
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4963
|
+
workerKey,
|
|
4964
|
+
leaseToken,
|
|
4965
|
+
kind: 'system',
|
|
4966
|
+
phase: artifactFailure ? 'static_game_artifact_failed' : 'static_game_publish_failed',
|
|
4967
|
+
message: artifactFailure
|
|
4968
|
+
? `Static game output was invalid: ${rawMessage}`
|
|
4969
|
+
: `Static game publishing failed: ${rawMessage}`,
|
|
4970
|
+
payload: { error: failureCode, detail: rawMessage },
|
|
4971
|
+
});
|
|
4972
|
+
retainWorkspace = true;
|
|
4973
|
+
const terminalReason = artifactFailure
|
|
4974
|
+
? {
|
|
4975
|
+
kind: 'failure',
|
|
4976
|
+
category: 'upload_validation',
|
|
4977
|
+
code: failureCode,
|
|
4978
|
+
message: 'The static game output did not pass validation.',
|
|
4979
|
+
retryable: false,
|
|
4980
|
+
rawError: rawMessage,
|
|
4981
|
+
source: 'worker',
|
|
4982
|
+
}
|
|
4983
|
+
: {
|
|
4984
|
+
kind: 'failure',
|
|
4985
|
+
category: 'instrument_error',
|
|
4986
|
+
code: failureCode,
|
|
4987
|
+
message: 'The platform failed to publish the static game.',
|
|
4988
|
+
retryable: true,
|
|
4989
|
+
rawError: rawMessage,
|
|
4990
|
+
source: 'worker',
|
|
4991
|
+
};
|
|
4992
|
+
await failTaskWithRetry(client, task.id, {
|
|
4993
|
+
workerKey,
|
|
4994
|
+
leaseToken,
|
|
4995
|
+
error: failureCode,
|
|
4996
|
+
terminalReason,
|
|
4997
|
+
result: {
|
|
4998
|
+
stage: artifactFailure ? 'artifact' : 'publish',
|
|
4999
|
+
error: rawMessage,
|
|
5000
|
+
agentRunSucceeded: true,
|
|
5001
|
+
},
|
|
5002
|
+
});
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
else if (hasAgentTaskUploadedArtifact(refreshed.task)) {
|
|
5006
|
+
console.error(`Agent task ${task.id} uploaded an artifact but exited before task completion; failing it explicitly.`);
|
|
5007
|
+
await failUploadedArtifactWithoutTaskDone();
|
|
4366
5008
|
}
|
|
4367
5009
|
else {
|
|
4368
5010
|
const tokenCap = (0, runtime_1.readPositiveEnvInt)('PLAYDROP_WORKER_TOKEN_CAP', runtime_1.DEFAULT_WORKER_TOKEN_CAP);
|
|
@@ -4716,6 +5358,9 @@ async function resolveTaskCommandContext(command, optionsEnv, taskContext) {
|
|
|
4716
5358
|
}
|
|
4717
5359
|
async function uploadTask(options = {}) {
|
|
4718
5360
|
const taskContext = readTaskContextFile();
|
|
5361
|
+
if (taskContext.kind === 'STATIC_GAME') {
|
|
5362
|
+
throw new Error('task_upload_not_allowed_for_static_game');
|
|
5363
|
+
}
|
|
4719
5364
|
if (taskContext.kind === 'GAME_REVIEW' || taskContext.kind === 'GAME_EVAL') {
|
|
4720
5365
|
throw new Error('task_upload_not_allowed_for_game_review');
|
|
4721
5366
|
}
|
|
@@ -4828,6 +5473,7 @@ async function completeTask(options) {
|
|
|
4828
5473
|
if (nextSteps === NEXT_STEPS_READ_FAILED) {
|
|
4829
5474
|
return;
|
|
4830
5475
|
}
|
|
5476
|
+
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile);
|
|
4831
5477
|
try {
|
|
4832
5478
|
await ctx.client.workerCompleteAgentTask(taskContext.taskId, {
|
|
4833
5479
|
taskToken: taskContext.taskToken,
|
|
@@ -4844,6 +5490,7 @@ async function completeTask(options) {
|
|
|
4844
5490
|
},
|
|
4845
5491
|
summary,
|
|
4846
5492
|
...(nextSteps !== undefined ? { nextSteps } : {}),
|
|
5493
|
+
...(platformFeedback ? { platformFeedback } : {}),
|
|
4847
5494
|
});
|
|
4848
5495
|
}
|
|
4849
5496
|
catch (error) {
|
|
@@ -4858,6 +5505,7 @@ async function completeTask(options) {
|
|
|
4858
5505
|
appName: uploadResult.appName,
|
|
4859
5506
|
version: uploadResult.version,
|
|
4860
5507
|
},
|
|
5508
|
+
...(platformFeedback ? { platformFeedback } : {}),
|
|
4861
5509
|
});
|
|
4862
5510
|
}
|
|
4863
5511
|
throw error;
|
|
@@ -4903,6 +5551,27 @@ function readOptionalTextFile(filePath) {
|
|
|
4903
5551
|
const content = (0, node_fs_1.readFileSync)(resolved, 'utf8').trim();
|
|
4904
5552
|
return content || null;
|
|
4905
5553
|
}
|
|
5554
|
+
function readPlatformFeedbackFile(filePath) {
|
|
5555
|
+
const normalizedPath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
5556
|
+
if (!normalizedPath) {
|
|
5557
|
+
return null;
|
|
5558
|
+
}
|
|
5559
|
+
try {
|
|
5560
|
+
const resolved = node_path_1.default.resolve(normalizedPath);
|
|
5561
|
+
if (!(0, node_fs_1.existsSync)(resolved)) {
|
|
5562
|
+
throw new Error('file_missing');
|
|
5563
|
+
}
|
|
5564
|
+
const content = (0, node_fs_1.readFileSync)(resolved, 'utf8').trim();
|
|
5565
|
+
if (!content || content.length > 10000) {
|
|
5566
|
+
throw new Error(content ? 'file_too_long' : 'file_empty');
|
|
5567
|
+
}
|
|
5568
|
+
return content;
|
|
5569
|
+
}
|
|
5570
|
+
catch (error) {
|
|
5571
|
+
console.warn(`Platform feedback was not attached: ${error instanceof Error ? error.message : String(error)}`);
|
|
5572
|
+
return null;
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
4906
5575
|
function contentTypeForEvidenceFile(fileName) {
|
|
4907
5576
|
const extension = node_path_1.default.extname(fileName).toLowerCase();
|
|
4908
5577
|
if (extension === '.png')
|
|
@@ -4981,6 +5650,7 @@ async function submitReviewTask(options) {
|
|
|
4981
5650
|
const creatorFeedback = state === 'ERROR'
|
|
4982
5651
|
? null
|
|
4983
5652
|
: readOptionalTextFile(creatorFeedbackFile ?? undefined);
|
|
5653
|
+
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile);
|
|
4984
5654
|
await (0, review_1.validateGameReviewResult)({
|
|
4985
5655
|
reviewState: state,
|
|
4986
5656
|
reviewMessage,
|
|
@@ -5009,6 +5679,7 @@ async function submitReviewTask(options) {
|
|
|
5009
5679
|
reviewState: state,
|
|
5010
5680
|
reviewMessage,
|
|
5011
5681
|
creatorFeedback,
|
|
5682
|
+
...(platformFeedback ? { platformFeedback } : {}),
|
|
5012
5683
|
...(nextSteps !== undefined ? { nextSteps } : {}),
|
|
5013
5684
|
evidenceFiles,
|
|
5014
5685
|
instrumentEvidence,
|
|
@@ -5136,11 +5807,13 @@ async function failTask(options) {
|
|
|
5136
5807
|
if (!ctx) {
|
|
5137
5808
|
return;
|
|
5138
5809
|
}
|
|
5810
|
+
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile);
|
|
5139
5811
|
await ctx.client.workerFailAgentTask(taskContext.taskId, {
|
|
5140
5812
|
taskToken: taskContext.taskToken,
|
|
5141
5813
|
error: message,
|
|
5142
5814
|
terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(message, 'agent'),
|
|
5143
5815
|
result: { failedBy: 'agent' },
|
|
5816
|
+
...(platformFeedback ? { platformFeedback } : {}),
|
|
5144
5817
|
});
|
|
5145
5818
|
(0, output_1.printSuccess)('Task marked failed. This was the final operation; send one short final status sentence and stop immediately.');
|
|
5146
5819
|
}
|