@playdrop/playdrop-cli 0.13.16 → 0.14.0
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/staticHtml.js +1 -0
- package/dist/commandContext.js +1 -1
- package/dist/commands/captureListing.d.ts +52 -0
- package/dist/commands/captureListing.js +213 -12
- package/dist/commands/upload.d.ts +12 -1
- package/dist/commands/upload.js +97 -88
- package/dist/commands/whoami.js +10 -1
- package/dist/commands/worker/runtime.js +2 -0
- package/dist/commands/worker.d.ts +28 -1
- package/dist/commands/worker.js +381 -29
- package/dist/index.js +17 -2
- package/dist/listingPreflight.d.ts +2 -1
- package/dist/listingPreflight.js +63 -35
- package/dist/workerAppProject.d.ts +2 -0
- package/dist/workerAppProject.js +33 -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 +27 -1
- 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 +103 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +4 -2
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +10 -0
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/types/dist/api.d.ts +150 -2
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +3 -1
- package/package.json +1 -1
package/dist/commands/worker.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.WORKER_TASK_WORKSPACE_RETENTION = exports.WORKER_CONTEXT_COMMAND_NOT_ALL
|
|
|
7
7
|
exports.usesCreatorRecovery = usesCreatorRecovery;
|
|
8
8
|
exports.buildCreatorRecoveryPrompt = buildCreatorRecoveryPrompt;
|
|
9
9
|
exports.buildGameEvalRetryPrompt = buildGameEvalRetryPrompt;
|
|
10
|
+
exports.resolveInitialCreatorProgress = resolveInitialCreatorProgress;
|
|
10
11
|
exports.allocateWorkerDevPort = allocateWorkerDevPort;
|
|
11
12
|
exports.isWorkerContextCommandAllowed = isWorkerContextCommandAllowed;
|
|
12
13
|
exports.resolveWorkerExecutionTargetFromRole = resolveWorkerExecutionTargetFromRole;
|
|
@@ -50,6 +51,7 @@ exports.removeAgentRunCredentials = removeAgentRunCredentials;
|
|
|
50
51
|
exports.removeStaleAgentRunCredentials = removeStaleAgentRunCredentials;
|
|
51
52
|
exports.mergeCreatorRecoveryResults = mergeCreatorRecoveryResults;
|
|
52
53
|
exports.buildAgentFailureCode = buildAgentFailureCode;
|
|
54
|
+
exports.classifyAgentAvailabilityFailureReason = classifyAgentAvailabilityFailureReason;
|
|
53
55
|
exports.describeAgentFailureForEvent = describeAgentFailureForEvent;
|
|
54
56
|
exports.createLocalPlaydropShim = createLocalPlaydropShim;
|
|
55
57
|
exports.parseCodexModelCatalog = parseCodexModelCatalog;
|
|
@@ -80,6 +82,8 @@ exports.assignmentRequiresRegisteredBundle = assignmentRequiresRegisteredBundle;
|
|
|
80
82
|
exports.startWorker = startWorker;
|
|
81
83
|
exports.reportTask = reportTask;
|
|
82
84
|
exports.showTaskContext = showTaskContext;
|
|
85
|
+
exports.assertTaskAssetMaterialIsTransparentPng = assertTaskAssetMaterialIsTransparentPng;
|
|
86
|
+
exports.materialTask = materialTask;
|
|
83
87
|
exports.reportCatalogueTask = reportCatalogueTask;
|
|
84
88
|
exports.uploadTask = uploadTask;
|
|
85
89
|
exports.claimSlugTask = claimSlugTask;
|
|
@@ -101,6 +105,7 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
101
105
|
const node_path_1 = __importDefault(require("node:path"));
|
|
102
106
|
const node_process_1 = __importDefault(require("node:process"));
|
|
103
107
|
const node_util_1 = require("node:util");
|
|
108
|
+
const sharp_1 = __importDefault(require("sharp"));
|
|
104
109
|
const apiClient_1 = require("../apiClient");
|
|
105
110
|
const clientInfo_1 = require("../clientInfo");
|
|
106
111
|
const commandContext_1 = require("../commandContext");
|
|
@@ -196,6 +201,7 @@ const WORKER_CONTEXT_ALLOWED_COMMANDS = [
|
|
|
196
201
|
['task', 'context'],
|
|
197
202
|
['task', 'help'],
|
|
198
203
|
['task', 'report'],
|
|
204
|
+
['task', 'material'],
|
|
199
205
|
['task', 'report-catalogue'],
|
|
200
206
|
['task', 'claim-slug'],
|
|
201
207
|
['task', 'upload'],
|
|
@@ -227,6 +233,33 @@ const WORKER_CONTEXT_ALLOWED_COMMANDS = [
|
|
|
227
233
|
['ai', 'generations'],
|
|
228
234
|
];
|
|
229
235
|
exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = 'worker_context_command_not_allowed: inside a PlayDrop worker task only task progress, task claim-slug, task upload/done/fail, assigned app scaffolding, project validation/build/dev/check/capture, read-only help/catalogue/documentation lookup, and PlayDrop AI generation are permitted.';
|
|
236
|
+
function resolveInitialCreatorProgress(kind, creationRecovery = false) {
|
|
237
|
+
if (creationRecovery) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
if (kind === 'NEW_GAME') {
|
|
241
|
+
return {
|
|
242
|
+
kind: 'progress',
|
|
243
|
+
phase: 'setup',
|
|
244
|
+
current: 'Reviewing your game idea',
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (kind === 'REMIX_GAME') {
|
|
248
|
+
return {
|
|
249
|
+
kind: 'progress',
|
|
250
|
+
phase: 'setup',
|
|
251
|
+
current: 'Reviewing the source game and your idea',
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (kind === 'GAME_UPDATE') {
|
|
255
|
+
return {
|
|
256
|
+
kind: 'progress',
|
|
257
|
+
phase: 'setup',
|
|
258
|
+
current: 'Reviewing your game and requested change',
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
230
263
|
function allocateWorkerDevPort(input) {
|
|
231
264
|
const basePort = input.basePort ?? DEFAULT_WORKER_DEV_PORT_BASE;
|
|
232
265
|
const maxParallelTasks = input.maxParallelTasks ?? DEFAULT_WORKER_MAX_PARALLEL_TASKS;
|
|
@@ -591,6 +624,13 @@ function buildWorkerTaskContext(input) {
|
|
|
591
624
|
const outputAppName = typeof output?.appName === 'string' && output.appName.trim()
|
|
592
625
|
? output.appName.trim()
|
|
593
626
|
: null;
|
|
627
|
+
const outputDisplayName = typeof output?.displayName === 'string' && output.displayName.trim()
|
|
628
|
+
? output.displayName.trim()
|
|
629
|
+
: null;
|
|
630
|
+
const outputSubtitle = typeof output?.subtitle === 'string' && output.subtitle.trim()
|
|
631
|
+
? output.subtitle.trim()
|
|
632
|
+
: null;
|
|
633
|
+
const outputPrimarySurface = normalizeWorkerInstrumentSurface(output?.primarySurface);
|
|
594
634
|
if (!creatorUsername || !outputVersion) {
|
|
595
635
|
throw new Error('agent_task_context_invalid');
|
|
596
636
|
}
|
|
@@ -621,6 +661,9 @@ function buildWorkerTaskContext(input) {
|
|
|
621
661
|
creatorRequest: input.prompt,
|
|
622
662
|
target: input.target,
|
|
623
663
|
outputAppName,
|
|
664
|
+
outputDisplayName,
|
|
665
|
+
outputSubtitle,
|
|
666
|
+
outputPrimarySurface,
|
|
624
667
|
outputVersion,
|
|
625
668
|
remixSourceRef,
|
|
626
669
|
allowedTemplateKeys,
|
|
@@ -703,6 +746,9 @@ function workerTaskContextPath(workspaceDir) {
|
|
|
703
746
|
function workerTaskUploadResultPath(workspaceDir) {
|
|
704
747
|
return node_path_1.default.join(workspaceDir, '.playdrop', 'task-upload-result.json');
|
|
705
748
|
}
|
|
749
|
+
function workerTaskMaterialsPath(workspaceDir) {
|
|
750
|
+
return node_path_1.default.join(workspaceDir, '.playdrop', 'task-materials.json');
|
|
751
|
+
}
|
|
706
752
|
function serializeWorkerTaskContext(taskContext) {
|
|
707
753
|
const playdrop = taskContext.metadata.playdrop;
|
|
708
754
|
if (!playdrop || typeof playdrop !== 'object' || Array.isArray(playdrop)) {
|
|
@@ -720,6 +766,9 @@ function serializeWorkerTaskContext(taskContext) {
|
|
|
720
766
|
output: {
|
|
721
767
|
...output,
|
|
722
768
|
appName: taskContext.outputAppName,
|
|
769
|
+
displayName: taskContext.outputDisplayName,
|
|
770
|
+
subtitle: taskContext.outputSubtitle,
|
|
771
|
+
primarySurface: taskContext.outputPrimarySurface,
|
|
723
772
|
},
|
|
724
773
|
},
|
|
725
774
|
};
|
|
@@ -887,6 +936,28 @@ function normalizeTaskClaimSlugDisplayName(value) {
|
|
|
887
936
|
}
|
|
888
937
|
return displayName;
|
|
889
938
|
}
|
|
939
|
+
function normalizeTaskClaimSlugSubtitle(value) {
|
|
940
|
+
if (value === undefined) {
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
const subtitle = typeof value === 'string' ? value.trim() : '';
|
|
944
|
+
if (!subtitle || subtitle.length > 120) {
|
|
945
|
+
throw new Error('invalid_app_subtitle');
|
|
946
|
+
}
|
|
947
|
+
return subtitle;
|
|
948
|
+
}
|
|
949
|
+
function normalizeTaskClaimSlugPrimarySurface(value) {
|
|
950
|
+
if (value === undefined) {
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
const primarySurface = typeof value === 'string' ? value.trim().toUpperCase() : '';
|
|
954
|
+
if (primarySurface !== 'DESKTOP'
|
|
955
|
+
&& primarySurface !== 'MOBILE_LANDSCAPE'
|
|
956
|
+
&& primarySurface !== 'MOBILE_PORTRAIT') {
|
|
957
|
+
throw new Error('invalid_primary_surface');
|
|
958
|
+
}
|
|
959
|
+
return primarySurface;
|
|
960
|
+
}
|
|
890
961
|
function readTaskNextStepsFile(filePath) {
|
|
891
962
|
const normalizedPath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
892
963
|
if (!normalizedPath) {
|
|
@@ -1447,11 +1518,7 @@ function resolveWorkerEventQueueDir() {
|
|
|
1447
1518
|
const eventDir = node_process_1.default.env.PLAYDROP_WORKER_EVENT_DIR?.trim();
|
|
1448
1519
|
return eventDir || null;
|
|
1449
1520
|
}
|
|
1450
|
-
function
|
|
1451
|
-
const eventDir = resolveWorkerEventQueueDir();
|
|
1452
|
-
if (!eventDir) {
|
|
1453
|
-
throw new Error('task_context_event_queue_missing');
|
|
1454
|
-
}
|
|
1521
|
+
function enqueueWorkerEvent(eventDir, event) {
|
|
1455
1522
|
(0, node_fs_1.mkdirSync)(eventDir, { recursive: true });
|
|
1456
1523
|
const eventId = `${Date.now()}-${crypto.randomUUID()}`;
|
|
1457
1524
|
const tmpPath = node_path_1.default.join(eventDir, `${eventId}.tmp`);
|
|
@@ -1459,6 +1526,13 @@ function enqueueWorkerContextEvent(event) {
|
|
|
1459
1526
|
(0, node_fs_1.writeFileSync)(tmpPath, JSON.stringify(event), 'utf8');
|
|
1460
1527
|
(0, node_fs_1.renameSync)(tmpPath, finalPath);
|
|
1461
1528
|
}
|
|
1529
|
+
function enqueueWorkerContextEvent(event) {
|
|
1530
|
+
const eventDir = resolveWorkerEventQueueDir();
|
|
1531
|
+
if (!eventDir) {
|
|
1532
|
+
throw new Error('task_context_event_queue_missing');
|
|
1533
|
+
}
|
|
1534
|
+
enqueueWorkerEvent(eventDir, event);
|
|
1535
|
+
}
|
|
1462
1536
|
function resolveWorkerTaskStateForWrite() {
|
|
1463
1537
|
const state = readWorkerTaskState();
|
|
1464
1538
|
if (!state) {
|
|
@@ -1838,6 +1912,9 @@ function buildCataloguePreviewPayload(cataloguePath, workspaceDir = node_path_1.
|
|
|
1838
1912
|
const displayName = normalizePreviewString(app.displayName);
|
|
1839
1913
|
if (displayName)
|
|
1840
1914
|
catalogue.displayName = displayName;
|
|
1915
|
+
const subtitle = normalizePreviewString(app.subtitle);
|
|
1916
|
+
if (subtitle)
|
|
1917
|
+
catalogue.subtitle = subtitle;
|
|
1841
1918
|
const description = normalizePreviewString(app.description);
|
|
1842
1919
|
if (description)
|
|
1843
1920
|
catalogue.description = description;
|
|
@@ -1850,6 +1927,9 @@ function buildCataloguePreviewPayload(cataloguePath, workspaceDir = node_path_1.
|
|
|
1850
1927
|
const version = normalizePreviewString(app.version);
|
|
1851
1928
|
if (version)
|
|
1852
1929
|
catalogue.version = version;
|
|
1930
|
+
const primarySurface = normalizePreviewString(app.primarySurface);
|
|
1931
|
+
if (primarySurface)
|
|
1932
|
+
catalogue.primarySurface = primarySurface;
|
|
1853
1933
|
const releaseNotes = normalizePreviewString(app.releaseNotes);
|
|
1854
1934
|
if (releaseNotes)
|
|
1855
1935
|
catalogue.releaseNotes = releaseNotes;
|
|
@@ -2486,18 +2566,36 @@ function buildAgentFailureCode(agent, result, request) {
|
|
|
2486
2566
|
if (combinedOutput.includes('agent_task_creation_recovery_timeout')) {
|
|
2487
2567
|
return `agent_task_creation_recovery_timeout:${provider}`;
|
|
2488
2568
|
}
|
|
2569
|
+
const providerStatus = extractProviderStatusCode(combinedOutput);
|
|
2570
|
+
if (/\b(?:usage|spending|token|credit)?\s*(?:limit|quota)\s+(?:has been |was )?(?:reached|exceeded|exhausted)\b/i.test(combinedOutput)
|
|
2571
|
+
|| /\byou(?:'ve| have) hit your (?:usage )?limit\b/i.test(combinedOutput)
|
|
2572
|
+
|| /\binsufficient_quota\b/i.test(combinedOutput)) {
|
|
2573
|
+
return `agent_provider_quota_exhausted:${provider}`;
|
|
2574
|
+
}
|
|
2575
|
+
if (/\b(?:not logged in|authentication required|please (?:run|use) (?:the )?(?:login|\/login)|oauth token expired)\b/i.test(combinedOutput)
|
|
2576
|
+
|| /\b(?:401|403)\s+(?:unauthorized|forbidden)\b/i.test(combinedOutput)) {
|
|
2577
|
+
return `agent_provider_not_authenticated:${provider}`;
|
|
2578
|
+
}
|
|
2489
2579
|
if (request
|
|
2490
2580
|
&& (/\b(?:unknown|invalid|unsupported)\s+(?:model|effort)\b/i.test(combinedOutput)
|
|
2491
2581
|
|| /\b(?:model|effort)\b.{0,80}\b(?:not found|not available|does not exist|is unavailable|is not supported)\b/i.test(combinedOutput))) {
|
|
2492
2582
|
return `requested_model_unavailable:${agent}:${request.model}:${request.reasoningEffort ?? ''}`;
|
|
2493
2583
|
}
|
|
2494
|
-
const providerStatus = extractProviderStatusCode(combinedOutput);
|
|
2495
2584
|
if (providerStatus === 529 || (/\b529\b/.test(combinedOutput) && /\boverloaded?\b/i.test(combinedOutput))) {
|
|
2496
2585
|
return `agent_provider_overloaded:${provider}:529`;
|
|
2497
2586
|
}
|
|
2498
|
-
if (/\boverloaded?\b/i.test(combinedOutput)
|
|
2587
|
+
if (/\boverloaded?\b/i.test(combinedOutput)
|
|
2588
|
+
|| /\b(?:selected\s+)?model\s+is\s+at\s+capacity\b/i.test(combinedOutput)) {
|
|
2499
2589
|
return `agent_provider_overloaded:${provider}`;
|
|
2500
2590
|
}
|
|
2591
|
+
if (providerStatus === 429
|
|
2592
|
+
|| providerStatus === 502
|
|
2593
|
+
|| providerStatus === 503
|
|
2594
|
+
|| providerStatus === 504
|
|
2595
|
+
|| /\b(?:service|provider|api)\s+(?:is\s+)?(?:temporarily\s+)?unavailable\b/i.test(combinedOutput)
|
|
2596
|
+
|| /\btoo many requests\b/i.test(combinedOutput)) {
|
|
2597
|
+
return `agent_provider_temporarily_unavailable:${provider}${providerStatus ? `:${providerStatus}` : ''}`;
|
|
2598
|
+
}
|
|
2501
2599
|
if (result.timedOut) {
|
|
2502
2600
|
return `agent_provider_timeout:${provider}`;
|
|
2503
2601
|
}
|
|
@@ -2509,6 +2607,22 @@ function buildAgentFailureCode(agent, result, request) {
|
|
|
2509
2607
|
}
|
|
2510
2608
|
return `agent_unknown_exit:${provider}`;
|
|
2511
2609
|
}
|
|
2610
|
+
function classifyAgentAvailabilityFailureReason(errorCode) {
|
|
2611
|
+
if (errorCode.startsWith('agent_provider_not_authenticated:')) {
|
|
2612
|
+
return 'agent_not_authenticated';
|
|
2613
|
+
}
|
|
2614
|
+
if (errorCode.startsWith('agent_provider_quota_exhausted:')) {
|
|
2615
|
+
return 'agent_quota_exhausted';
|
|
2616
|
+
}
|
|
2617
|
+
if (errorCode.startsWith('agent_provider_overloaded:')
|
|
2618
|
+
|| errorCode.startsWith('agent_provider_temporarily_unavailable:')) {
|
|
2619
|
+
return 'agent_temporarily_unavailable';
|
|
2620
|
+
}
|
|
2621
|
+
if (errorCode.startsWith('requested_model_unavailable:')) {
|
|
2622
|
+
return 'agent_model_unsupported';
|
|
2623
|
+
}
|
|
2624
|
+
return null;
|
|
2625
|
+
}
|
|
2512
2626
|
function describeAgentFailureForEvent(errorCode) {
|
|
2513
2627
|
const [code, provider, detail] = errorCode.split(':');
|
|
2514
2628
|
if (code === 'requested_model_unavailable') {
|
|
@@ -2517,8 +2631,19 @@ function describeAgentFailureForEvent(errorCode) {
|
|
|
2517
2631
|
const providerLabel = provider === 'claude' ? 'Claude' : provider === 'codex' ? 'Codex' : 'The agent provider';
|
|
2518
2632
|
if (code === 'agent_provider_overloaded') {
|
|
2519
2633
|
return detail
|
|
2520
|
-
? `${providerLabel} is overloaded (API ${detail}).
|
|
2521
|
-
: `${providerLabel} is overloaded.
|
|
2634
|
+
? `${providerLabel} is overloaded (API ${detail}). Reporting provider unavailability to the server.`
|
|
2635
|
+
: `${providerLabel} is overloaded. Reporting provider unavailability to the server.`;
|
|
2636
|
+
}
|
|
2637
|
+
if (code === 'agent_provider_quota_exhausted') {
|
|
2638
|
+
return `${providerLabel} has reached its usage limit. Reporting provider unavailability to the server.`;
|
|
2639
|
+
}
|
|
2640
|
+
if (code === 'agent_provider_not_authenticated') {
|
|
2641
|
+
return `${providerLabel} is not authenticated on this worker. Reporting provider unavailability to the server.`;
|
|
2642
|
+
}
|
|
2643
|
+
if (code === 'agent_provider_temporarily_unavailable') {
|
|
2644
|
+
return detail
|
|
2645
|
+
? `${providerLabel} is temporarily unavailable (API ${detail}). Reporting provider unavailability to the server.`
|
|
2646
|
+
: `${providerLabel} is temporarily unavailable. Reporting provider unavailability to the server.`;
|
|
2522
2647
|
}
|
|
2523
2648
|
if (code === 'agent_provider_timeout') {
|
|
2524
2649
|
return `${providerLabel} timed out before completing the task. Retry this task when the provider is healthy.`;
|
|
@@ -4496,6 +4621,27 @@ async function startWorker(options = {}) {
|
|
|
4496
4621
|
if (shuttingDown) {
|
|
4497
4622
|
throw new Error('worker_shutdown');
|
|
4498
4623
|
}
|
|
4624
|
+
let initialCreatorProgressAttempted = false;
|
|
4625
|
+
const handleAgentChild = (controls) => {
|
|
4626
|
+
activeTerminators.set(task.id, controls.terminate);
|
|
4627
|
+
if (initialCreatorProgressAttempted) {
|
|
4628
|
+
return;
|
|
4629
|
+
}
|
|
4630
|
+
initialCreatorProgressAttempted = true;
|
|
4631
|
+
const initialProgress = resolveInitialCreatorProgress(task.kind, startsInCreationRecovery);
|
|
4632
|
+
if (!initialProgress) {
|
|
4633
|
+
return;
|
|
4634
|
+
}
|
|
4635
|
+
try {
|
|
4636
|
+
enqueueWorkerEvent(eventDir, initialProgress);
|
|
4637
|
+
queueEventDrain().catch((error) => {
|
|
4638
|
+
handleEventDrainFailure(error);
|
|
4639
|
+
});
|
|
4640
|
+
}
|
|
4641
|
+
catch (error) {
|
|
4642
|
+
console.error(`Warning: initial creator progress was not queued: ${error instanceof Error ? error.message : String(error)}`);
|
|
4643
|
+
}
|
|
4644
|
+
};
|
|
4499
4645
|
const runAssignedAgent = async (input) => {
|
|
4500
4646
|
if (assignment.agent.runtime === 'CODEX') {
|
|
4501
4647
|
return runCodex({
|
|
@@ -4518,9 +4664,7 @@ async function startWorker(options = {}) {
|
|
|
4518
4664
|
onTranscriptChunks: async (chunks) => {
|
|
4519
4665
|
await appendTranscriptChunks(chunks);
|
|
4520
4666
|
},
|
|
4521
|
-
onChild:
|
|
4522
|
-
activeTerminators.set(task.id, controls.terminate);
|
|
4523
|
-
},
|
|
4667
|
+
onChild: handleAgentChild,
|
|
4524
4668
|
cleanRoom,
|
|
4525
4669
|
timeoutMs: input.timeoutMs,
|
|
4526
4670
|
resumeSessionId: input.resumeSessionId,
|
|
@@ -4548,9 +4692,7 @@ async function startWorker(options = {}) {
|
|
|
4548
4692
|
onTranscriptChunks: async (chunks) => {
|
|
4549
4693
|
await appendTranscriptChunks(chunks);
|
|
4550
4694
|
},
|
|
4551
|
-
onChild:
|
|
4552
|
-
activeTerminators.set(task.id, controls.terminate);
|
|
4553
|
-
},
|
|
4695
|
+
onChild: handleAgentChild,
|
|
4554
4696
|
cleanRoom,
|
|
4555
4697
|
timeoutMs: input.timeoutMs,
|
|
4556
4698
|
resumeSessionId: input.resumeSessionId,
|
|
@@ -4560,7 +4702,6 @@ async function startWorker(options = {}) {
|
|
|
4560
4702
|
await client.workerRecordAgentTaskAttemptUnavailable(task.id, {
|
|
4561
4703
|
workerKey,
|
|
4562
4704
|
leaseToken,
|
|
4563
|
-
attemptIndex: 0,
|
|
4564
4705
|
reason: 'unavailable_runtime',
|
|
4565
4706
|
message: 'Cursor Composer noninteractive worker execution is not configured on this worker.',
|
|
4566
4707
|
});
|
|
@@ -4686,6 +4827,7 @@ async function startWorker(options = {}) {
|
|
|
4686
4827
|
model: assignment.agent.model,
|
|
4687
4828
|
reasoningEffort,
|
|
4688
4829
|
});
|
|
4830
|
+
const availabilityReason = classifyAgentAvailabilityFailureReason(failureCode);
|
|
4689
4831
|
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4690
4832
|
workerKey,
|
|
4691
4833
|
leaseToken,
|
|
@@ -4695,14 +4837,32 @@ async function startWorker(options = {}) {
|
|
|
4695
4837
|
pct: null,
|
|
4696
4838
|
payload: { error: failureCode },
|
|
4697
4839
|
});
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4840
|
+
if (availabilityReason) {
|
|
4841
|
+
retainWorkspace = false;
|
|
4842
|
+
await reportTelemetry('FAILED').catch((error) => {
|
|
4843
|
+
console.error(`Agent task ${task.id} telemetry failed before provider handoff: ${error instanceof Error ? error.message : String(error)}`);
|
|
4844
|
+
});
|
|
4845
|
+
const unavailable = await client.workerRecordAgentTaskAttemptUnavailable(task.id, {
|
|
4846
|
+
workerKey,
|
|
4847
|
+
leaseToken,
|
|
4848
|
+
reason: availabilityReason,
|
|
4849
|
+
message: failureCode,
|
|
4850
|
+
});
|
|
4851
|
+
fenced = true;
|
|
4852
|
+
console.error(unavailable.requeued
|
|
4853
|
+
? `Agent task ${task.id} reported ${availabilityReason}; the server queued the next approved attempt.`
|
|
4854
|
+
: `Agent task ${task.id} reported ${availabilityReason}; no approved attempts remain.`);
|
|
4855
|
+
}
|
|
4856
|
+
else {
|
|
4857
|
+
await failTaskWithRetry(client, task.id, {
|
|
4858
|
+
workerKey,
|
|
4859
|
+
leaseToken,
|
|
4860
|
+
error: failureCode,
|
|
4861
|
+
terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(failureCode, 'agent'),
|
|
4862
|
+
result: agentRunResult,
|
|
4863
|
+
});
|
|
4864
|
+
await reportTelemetry('FAILED');
|
|
4865
|
+
}
|
|
4706
4866
|
}
|
|
4707
4867
|
}
|
|
4708
4868
|
else {
|
|
@@ -5114,7 +5274,7 @@ async function reportTask(options) {
|
|
|
5114
5274
|
const done = options.done?.trim() || null;
|
|
5115
5275
|
const current = options.current?.trim() || null;
|
|
5116
5276
|
if (!message && !done && !current) {
|
|
5117
|
-
(0, messages_1.printErrorWithHelp)('Task report requires a message, done, or current value.', ['Example: playdrop task report --phase
|
|
5277
|
+
(0, messages_1.printErrorWithHelp)('Task report requires a message, done, or current value.', ['Example: playdrop task report --phase greybox-core-loop --done "Built the first loop" --current "Tuning controls"'], { command: 'task report' });
|
|
5118
5278
|
node_process_1.default.exitCode = 1;
|
|
5119
5279
|
return;
|
|
5120
5280
|
}
|
|
@@ -5150,6 +5310,131 @@ function showTaskContext() {
|
|
|
5150
5310
|
},
|
|
5151
5311
|
}, null, 2));
|
|
5152
5312
|
}
|
|
5313
|
+
function readWorkerTaskMaterialReceipts(workspaceDir) {
|
|
5314
|
+
const receiptPath = workerTaskMaterialsPath(workspaceDir);
|
|
5315
|
+
if (!(0, node_fs_1.existsSync)(receiptPath)) {
|
|
5316
|
+
return [];
|
|
5317
|
+
}
|
|
5318
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(receiptPath, 'utf8'));
|
|
5319
|
+
if (!Array.isArray(parsed)) {
|
|
5320
|
+
throw new Error('task_material_receipts_invalid');
|
|
5321
|
+
}
|
|
5322
|
+
return parsed.map((entry) => {
|
|
5323
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
5324
|
+
throw new Error('task_material_receipts_invalid');
|
|
5325
|
+
}
|
|
5326
|
+
const candidate = entry;
|
|
5327
|
+
const filePath = typeof candidate.filePath === 'string' ? candidate.filePath.trim() : '';
|
|
5328
|
+
const sha256 = typeof candidate.sha256 === 'string' ? candidate.sha256.trim() : '';
|
|
5329
|
+
const presentation = candidate.presentation;
|
|
5330
|
+
const title = typeof candidate.title === 'string' ? candidate.title.trim() : '';
|
|
5331
|
+
if (!node_path_1.default.isAbsolute(filePath)
|
|
5332
|
+
|| !/^[a-f0-9]{64}$/.test(sha256)
|
|
5333
|
+
|| (presentation !== 'frame' && presentation !== 'asset')
|
|
5334
|
+
|| !title) {
|
|
5335
|
+
throw new Error('task_material_receipts_invalid');
|
|
5336
|
+
}
|
|
5337
|
+
return { filePath, sha256, presentation, title };
|
|
5338
|
+
});
|
|
5339
|
+
}
|
|
5340
|
+
function recordWorkerTaskMaterialReceipt(workspaceDir, receipt) {
|
|
5341
|
+
const receipts = readWorkerTaskMaterialReceipts(workspaceDir);
|
|
5342
|
+
const retained = receipts.filter((entry) => (entry.filePath !== receipt.filePath
|
|
5343
|
+
|| entry.presentation !== receipt.presentation));
|
|
5344
|
+
retained.push(receipt);
|
|
5345
|
+
(0, node_fs_1.writeFileSync)(workerTaskMaterialsPath(workspaceDir), `${JSON.stringify(retained, null, 2)}\n`, 'utf8');
|
|
5346
|
+
}
|
|
5347
|
+
async function assertTaskAssetMaterialIsTransparentPng(filePath) {
|
|
5348
|
+
let metadata;
|
|
5349
|
+
let stats;
|
|
5350
|
+
try {
|
|
5351
|
+
metadata = await (0, sharp_1.default)(filePath, { failOn: 'error' }).metadata();
|
|
5352
|
+
stats = await (0, sharp_1.default)(filePath, { failOn: 'error' }).stats();
|
|
5353
|
+
}
|
|
5354
|
+
catch {
|
|
5355
|
+
throw new Error(`task_material_asset_decode_failed:${filePath}`);
|
|
5356
|
+
}
|
|
5357
|
+
if (metadata.format !== 'png') {
|
|
5358
|
+
throw new Error(`task_material_asset_must_be_png:${filePath}`);
|
|
5359
|
+
}
|
|
5360
|
+
if (metadata.hasAlpha !== true || stats.isOpaque) {
|
|
5361
|
+
throw new Error(`task_material_asset_background_not_removed:${filePath}`);
|
|
5362
|
+
}
|
|
5363
|
+
}
|
|
5364
|
+
async function materialTask(options) {
|
|
5365
|
+
try {
|
|
5366
|
+
const fileOption = options.file?.trim() ?? '';
|
|
5367
|
+
const title = options.title?.trim() ?? '';
|
|
5368
|
+
if (!fileOption || !title || title.length > 60 || title.split(/\s+/).length > 3) {
|
|
5369
|
+
throw new Error('task_material_file_and_title_required');
|
|
5370
|
+
}
|
|
5371
|
+
const filePath = node_path_1.default.resolve(fileOption);
|
|
5372
|
+
if (!(0, node_fs_1.existsSync)(filePath) || !(0, node_fs_1.statSync)(filePath).isFile()) {
|
|
5373
|
+
throw new Error(`task_material_file_not_found:${fileOption}`);
|
|
5374
|
+
}
|
|
5375
|
+
const posterOption = options.poster?.trim() ?? '';
|
|
5376
|
+
const posterPath = posterOption ? node_path_1.default.resolve(posterOption) : null;
|
|
5377
|
+
if (posterPath && (!(0, node_fs_1.existsSync)(posterPath) || !(0, node_fs_1.statSync)(posterPath).isFile())) {
|
|
5378
|
+
throw new Error(`task_material_poster_not_found:${posterOption}`);
|
|
5379
|
+
}
|
|
5380
|
+
const presentation = options.asset ? 'asset' : 'frame';
|
|
5381
|
+
if (presentation === 'asset') {
|
|
5382
|
+
if (posterPath) {
|
|
5383
|
+
throw new Error('task_material_asset_poster_not_allowed');
|
|
5384
|
+
}
|
|
5385
|
+
await assertTaskAssetMaterialIsTransparentPng(filePath);
|
|
5386
|
+
}
|
|
5387
|
+
const taskContext = readTaskContextFile();
|
|
5388
|
+
const ctx = await resolveTaskCommandContext('task material', options.env, taskContext);
|
|
5389
|
+
if (!ctx) {
|
|
5390
|
+
throw new Error('task_material_context_unavailable');
|
|
5391
|
+
}
|
|
5392
|
+
const fileBuffer = (0, node_fs_1.readFileSync)(filePath);
|
|
5393
|
+
const posterBuffer = posterPath ? (0, node_fs_1.readFileSync)(posterPath) : null;
|
|
5394
|
+
const clientKey = (0, node_crypto_1.createHash)('sha256')
|
|
5395
|
+
.update([
|
|
5396
|
+
String(taskContext.taskId),
|
|
5397
|
+
String(taskContext.attempt),
|
|
5398
|
+
node_path_1.default.relative(node_process_1.default.cwd(), filePath),
|
|
5399
|
+
(0, node_crypto_1.createHash)('sha256').update(fileBuffer).digest('hex'),
|
|
5400
|
+
title,
|
|
5401
|
+
posterBuffer ? (0, node_crypto_1.createHash)('sha256').update(posterBuffer).digest('hex') : '',
|
|
5402
|
+
presentation,
|
|
5403
|
+
].join(':'))
|
|
5404
|
+
.digest('hex')
|
|
5405
|
+
.slice(0, 40);
|
|
5406
|
+
await ctx.client.workerUploadAgentTaskMaterial(taskContext.taskId, {
|
|
5407
|
+
file: new Blob([new Uint8Array(fileBuffer)]),
|
|
5408
|
+
fileName: node_path_1.default.basename(filePath),
|
|
5409
|
+
title,
|
|
5410
|
+
clientKey,
|
|
5411
|
+
presentation,
|
|
5412
|
+
phase: options.phase?.trim() || undefined,
|
|
5413
|
+
taskToken: taskContext.taskToken,
|
|
5414
|
+
...(posterBuffer && posterPath
|
|
5415
|
+
? {
|
|
5416
|
+
poster: new Blob([new Uint8Array(posterBuffer)]),
|
|
5417
|
+
posterFileName: node_path_1.default.basename(posterPath),
|
|
5418
|
+
}
|
|
5419
|
+
: {}),
|
|
5420
|
+
});
|
|
5421
|
+
recordWorkerTaskMaterialReceipt(resolveTaskWorkspaceDir(), {
|
|
5422
|
+
filePath,
|
|
5423
|
+
sha256: (0, node_crypto_1.createHash)('sha256').update(fileBuffer).digest('hex'),
|
|
5424
|
+
presentation,
|
|
5425
|
+
title,
|
|
5426
|
+
});
|
|
5427
|
+
(0, output_1.printSuccess)(`Task material shared: ${title}`);
|
|
5428
|
+
}
|
|
5429
|
+
catch (error) {
|
|
5430
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5431
|
+
const suggestion = message.startsWith('task_material_asset_background_not_removed:')
|
|
5432
|
+
? ' Remove the background and retry with --asset, or omit --asset if the image is honestly useful as a full frame.'
|
|
5433
|
+
: '';
|
|
5434
|
+
console.warn(`Warning: task material was not shared: ${message}.${suggestion}`);
|
|
5435
|
+
node_process_1.default.exitCode = 0;
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5153
5438
|
async function reportCatalogueTask(options) {
|
|
5154
5439
|
const message = options.message?.trim();
|
|
5155
5440
|
if (!message) {
|
|
@@ -5216,6 +5501,38 @@ async function uploadTask(options = {}) {
|
|
|
5216
5501
|
}
|
|
5217
5502
|
const uploadKind = taskContext.kind;
|
|
5218
5503
|
const workspaceDir = resolveTaskWorkspaceDir();
|
|
5504
|
+
if (options.preflightOnly) {
|
|
5505
|
+
const ctx = await resolveTaskCommandContext('task upload --preflight-only', options.env, taskContext);
|
|
5506
|
+
if (!ctx) {
|
|
5507
|
+
return;
|
|
5508
|
+
}
|
|
5509
|
+
const projectDir = discoverWorkerProjectRoot(node_process_1.default.cwd());
|
|
5510
|
+
const result = await (0, upload_1.preflightWorkerAppProject)({
|
|
5511
|
+
client: ctx.client,
|
|
5512
|
+
taskId: taskContext.taskId,
|
|
5513
|
+
taskToken: taskContext.taskToken,
|
|
5514
|
+
kind: uploadKind,
|
|
5515
|
+
executionTarget: taskContext.target,
|
|
5516
|
+
expectedAppName: taskContext.outputAppName ?? undefined,
|
|
5517
|
+
expectedDisplayName: taskContext.outputDisplayName,
|
|
5518
|
+
expectedSubtitle: taskContext.outputSubtitle,
|
|
5519
|
+
expectedPrimarySurface: taskContext.outputPrimarySurface,
|
|
5520
|
+
remixSourceRef: taskContext.remixSourceRef ?? null,
|
|
5521
|
+
playdropAssetRequirement: (0, upload_1.resolveWorkerPlaydropAssetRequirement)(taskContext.creatorRequest),
|
|
5522
|
+
creatorRequest: taskContext.creatorRequest,
|
|
5523
|
+
projectDir,
|
|
5524
|
+
creatorUsername: taskContext.creatorUsername,
|
|
5525
|
+
apiBase: ctx.envConfig.apiBase,
|
|
5526
|
+
webBase: ctx.envConfig.webBase ?? null,
|
|
5527
|
+
token: ctx.token,
|
|
5528
|
+
user: ctx.user,
|
|
5529
|
+
});
|
|
5530
|
+
for (const warning of result.warnings) {
|
|
5531
|
+
console.error(`Preflight warning: ${warning}`);
|
|
5532
|
+
}
|
|
5533
|
+
(0, output_1.printSuccess)(`Task upload preflight passed for ${result.creatorUsername}/${result.appName}. Final capture can start.`);
|
|
5534
|
+
return;
|
|
5535
|
+
}
|
|
5219
5536
|
if ((0, node_fs_1.existsSync)(workerTaskUploadResultPath(workspaceDir))) {
|
|
5220
5537
|
const existingResult = readTaskUploadResultFile(workspaceDir);
|
|
5221
5538
|
assertTaskUploadResultMatchesContext({
|
|
@@ -5238,6 +5555,9 @@ async function uploadTask(options = {}) {
|
|
|
5238
5555
|
kind: uploadKind,
|
|
5239
5556
|
executionTarget: taskContext.target,
|
|
5240
5557
|
expectedAppName: taskContext.outputAppName ?? undefined,
|
|
5558
|
+
expectedDisplayName: taskContext.outputDisplayName,
|
|
5559
|
+
expectedSubtitle: taskContext.outputSubtitle,
|
|
5560
|
+
expectedPrimarySurface: taskContext.outputPrimarySurface,
|
|
5241
5561
|
remixSourceRef: taskContext.remixSourceRef ?? null,
|
|
5242
5562
|
playdropAssetRequirement: (0, upload_1.resolveWorkerPlaydropAssetRequirement)(taskContext.creatorRequest),
|
|
5243
5563
|
creatorRequest: taskContext.creatorRequest,
|
|
@@ -5271,26 +5591,55 @@ async function claimSlugTask(options) {
|
|
|
5271
5591
|
}
|
|
5272
5592
|
const appName = normalizeTaskClaimSlugAppName(options.appName);
|
|
5273
5593
|
const displayName = normalizeTaskClaimSlugDisplayName(options.displayName);
|
|
5594
|
+
const subtitle = normalizeTaskClaimSlugSubtitle(options.subtitle);
|
|
5595
|
+
const primarySurface = normalizeTaskClaimSlugPrimarySurface(options.primarySurface);
|
|
5274
5596
|
const ctx = await resolveTaskCommandContext('task claim-slug', options.env, taskContext);
|
|
5275
5597
|
if (!ctx) {
|
|
5276
5598
|
return;
|
|
5277
5599
|
}
|
|
5278
|
-
const
|
|
5600
|
+
const request = {
|
|
5279
5601
|
appName,
|
|
5280
5602
|
displayName,
|
|
5281
|
-
|
|
5603
|
+
...(subtitle ? { subtitle } : {}),
|
|
5604
|
+
...(primarySurface ? { primarySurface } : {}),
|
|
5605
|
+
};
|
|
5606
|
+
const response = await ctx.client.workerClaimAgentTaskSlug(taskContext.taskId, request);
|
|
5282
5607
|
const claimedAppName = typeof response.appName === 'string' && response.appName.trim()
|
|
5283
5608
|
? response.appName.trim()
|
|
5284
5609
|
: '';
|
|
5285
5610
|
if (claimedAppName !== appName) {
|
|
5286
5611
|
throw new Error('task_claim_slug_response_mismatch');
|
|
5287
5612
|
}
|
|
5613
|
+
const hasResponseDisplayName = Object.prototype.hasOwnProperty.call(response, 'displayName');
|
|
5614
|
+
const responseDisplayName = typeof response.displayName === 'string' && response.displayName.trim()
|
|
5615
|
+
? response.displayName.trim()
|
|
5616
|
+
: '';
|
|
5617
|
+
if (hasResponseDisplayName && responseDisplayName !== displayName) {
|
|
5618
|
+
throw new Error('task_claim_identity_response_mismatch');
|
|
5619
|
+
}
|
|
5620
|
+
const hasResponseSubtitle = Object.prototype.hasOwnProperty.call(response, 'subtitle');
|
|
5621
|
+
const responseSubtitle = typeof response.subtitle === 'string' && response.subtitle.trim()
|
|
5622
|
+
? response.subtitle.trim()
|
|
5623
|
+
: null;
|
|
5624
|
+
const hasResponsePrimarySurface = Object.prototype.hasOwnProperty.call(response, 'primarySurface');
|
|
5625
|
+
const responsePrimarySurface = normalizeWorkerInstrumentSurface(response.primarySurface);
|
|
5626
|
+
if ((subtitle && hasResponseSubtitle && responseSubtitle !== subtitle)
|
|
5627
|
+
|| (primarySurface && hasResponsePrimarySurface && responsePrimarySurface !== primarySurface)) {
|
|
5628
|
+
throw new Error('task_claim_identity_response_mismatch');
|
|
5629
|
+
}
|
|
5630
|
+
const claimedSubtitle = subtitle ?? responseSubtitle ?? taskContext.outputSubtitle;
|
|
5631
|
+
const claimedPrimarySurface = primarySurface ?? responsePrimarySurface ?? taskContext.outputPrimarySurface;
|
|
5288
5632
|
const workspaceDir = resolveTaskWorkspaceDir();
|
|
5289
5633
|
await writeWorkerTaskContextFile(workspaceDir, {
|
|
5290
5634
|
...taskContext,
|
|
5291
5635
|
outputAppName: claimedAppName,
|
|
5636
|
+
outputDisplayName: displayName,
|
|
5637
|
+
outputSubtitle: claimedSubtitle,
|
|
5638
|
+
outputPrimarySurface: claimedPrimarySurface,
|
|
5292
5639
|
});
|
|
5293
|
-
(0, output_1.printSuccess)(
|
|
5640
|
+
(0, output_1.printSuccess)(claimedPrimarySurface
|
|
5641
|
+
? `Claimed "${displayName}" (${claimedAppName}) for ${claimedPrimarySurface}.`
|
|
5642
|
+
: `Claimed app slug "${claimedAppName}" for ${displayName}.`);
|
|
5294
5643
|
}
|
|
5295
5644
|
async function completeTask(options) {
|
|
5296
5645
|
const taskContext = readTaskContextFile();
|
|
@@ -5298,9 +5647,12 @@ async function completeTask(options) {
|
|
|
5298
5647
|
throw new Error('task_done_not_allowed_for_game_review');
|
|
5299
5648
|
}
|
|
5300
5649
|
const summary = typeof options.summary === 'string' ? options.summary.trim() : '';
|
|
5301
|
-
if (!summary ||
|
|
5650
|
+
if (!summary || /[\r\n]/.test(summary)) {
|
|
5302
5651
|
throw new Error('task_done_summary_required');
|
|
5303
5652
|
}
|
|
5653
|
+
if (summary.length > 200) {
|
|
5654
|
+
throw new Error('task_done_summary_too_long:max_200_characters');
|
|
5655
|
+
}
|
|
5304
5656
|
const ctx = await resolveTaskCommandContext('task done', options.env, taskContext);
|
|
5305
5657
|
if (!ctx) {
|
|
5306
5658
|
return;
|