@playdrop/playdrop-cli 0.13.15 → 0.13.17
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 +43 -0
- package/dist/commands/captureListing.js +184 -5
- 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 +376 -28
- package/dist/index.js +17 -2
- package/dist/listingPreflight.d.ts +2 -1
- package/dist/listingPreflight.js +49 -18
- 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,35 @@ 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
2587
|
if (/\boverloaded?\b/i.test(combinedOutput)) {
|
|
2499
2588
|
return `agent_provider_overloaded:${provider}`;
|
|
2500
2589
|
}
|
|
2590
|
+
if (providerStatus === 429
|
|
2591
|
+
|| providerStatus === 502
|
|
2592
|
+
|| providerStatus === 503
|
|
2593
|
+
|| providerStatus === 504
|
|
2594
|
+
|| /\b(?:service|provider|api)\s+(?:is\s+)?(?:temporarily\s+)?unavailable\b/i.test(combinedOutput)
|
|
2595
|
+
|| /\btoo many requests\b/i.test(combinedOutput)) {
|
|
2596
|
+
return `agent_provider_temporarily_unavailable:${provider}${providerStatus ? `:${providerStatus}` : ''}`;
|
|
2597
|
+
}
|
|
2501
2598
|
if (result.timedOut) {
|
|
2502
2599
|
return `agent_provider_timeout:${provider}`;
|
|
2503
2600
|
}
|
|
@@ -2509,6 +2606,22 @@ function buildAgentFailureCode(agent, result, request) {
|
|
|
2509
2606
|
}
|
|
2510
2607
|
return `agent_unknown_exit:${provider}`;
|
|
2511
2608
|
}
|
|
2609
|
+
function classifyAgentAvailabilityFailureReason(errorCode) {
|
|
2610
|
+
if (errorCode.startsWith('agent_provider_not_authenticated:')) {
|
|
2611
|
+
return 'agent_not_authenticated';
|
|
2612
|
+
}
|
|
2613
|
+
if (errorCode.startsWith('agent_provider_quota_exhausted:')) {
|
|
2614
|
+
return 'agent_quota_exhausted';
|
|
2615
|
+
}
|
|
2616
|
+
if (errorCode.startsWith('agent_provider_overloaded:')
|
|
2617
|
+
|| errorCode.startsWith('agent_provider_temporarily_unavailable:')) {
|
|
2618
|
+
return 'agent_temporarily_unavailable';
|
|
2619
|
+
}
|
|
2620
|
+
if (errorCode.startsWith('requested_model_unavailable:')) {
|
|
2621
|
+
return 'agent_model_unsupported';
|
|
2622
|
+
}
|
|
2623
|
+
return null;
|
|
2624
|
+
}
|
|
2512
2625
|
function describeAgentFailureForEvent(errorCode) {
|
|
2513
2626
|
const [code, provider, detail] = errorCode.split(':');
|
|
2514
2627
|
if (code === 'requested_model_unavailable') {
|
|
@@ -2517,8 +2630,19 @@ function describeAgentFailureForEvent(errorCode) {
|
|
|
2517
2630
|
const providerLabel = provider === 'claude' ? 'Claude' : provider === 'codex' ? 'Codex' : 'The agent provider';
|
|
2518
2631
|
if (code === 'agent_provider_overloaded') {
|
|
2519
2632
|
return detail
|
|
2520
|
-
? `${providerLabel} is overloaded (API ${detail}).
|
|
2521
|
-
: `${providerLabel} is overloaded.
|
|
2633
|
+
? `${providerLabel} is overloaded (API ${detail}). Reporting provider unavailability to the server.`
|
|
2634
|
+
: `${providerLabel} is overloaded. Reporting provider unavailability to the server.`;
|
|
2635
|
+
}
|
|
2636
|
+
if (code === 'agent_provider_quota_exhausted') {
|
|
2637
|
+
return `${providerLabel} has reached its usage limit. Reporting provider unavailability to the server.`;
|
|
2638
|
+
}
|
|
2639
|
+
if (code === 'agent_provider_not_authenticated') {
|
|
2640
|
+
return `${providerLabel} is not authenticated on this worker. Reporting provider unavailability to the server.`;
|
|
2641
|
+
}
|
|
2642
|
+
if (code === 'agent_provider_temporarily_unavailable') {
|
|
2643
|
+
return detail
|
|
2644
|
+
? `${providerLabel} is temporarily unavailable (API ${detail}). Reporting provider unavailability to the server.`
|
|
2645
|
+
: `${providerLabel} is temporarily unavailable. Reporting provider unavailability to the server.`;
|
|
2522
2646
|
}
|
|
2523
2647
|
if (code === 'agent_provider_timeout') {
|
|
2524
2648
|
return `${providerLabel} timed out before completing the task. Retry this task when the provider is healthy.`;
|
|
@@ -4496,6 +4620,27 @@ async function startWorker(options = {}) {
|
|
|
4496
4620
|
if (shuttingDown) {
|
|
4497
4621
|
throw new Error('worker_shutdown');
|
|
4498
4622
|
}
|
|
4623
|
+
let initialCreatorProgressAttempted = false;
|
|
4624
|
+
const handleAgentChild = (controls) => {
|
|
4625
|
+
activeTerminators.set(task.id, controls.terminate);
|
|
4626
|
+
if (initialCreatorProgressAttempted) {
|
|
4627
|
+
return;
|
|
4628
|
+
}
|
|
4629
|
+
initialCreatorProgressAttempted = true;
|
|
4630
|
+
const initialProgress = resolveInitialCreatorProgress(task.kind, startsInCreationRecovery);
|
|
4631
|
+
if (!initialProgress) {
|
|
4632
|
+
return;
|
|
4633
|
+
}
|
|
4634
|
+
try {
|
|
4635
|
+
enqueueWorkerEvent(eventDir, initialProgress);
|
|
4636
|
+
queueEventDrain().catch((error) => {
|
|
4637
|
+
handleEventDrainFailure(error);
|
|
4638
|
+
});
|
|
4639
|
+
}
|
|
4640
|
+
catch (error) {
|
|
4641
|
+
console.error(`Warning: initial creator progress was not queued: ${error instanceof Error ? error.message : String(error)}`);
|
|
4642
|
+
}
|
|
4643
|
+
};
|
|
4499
4644
|
const runAssignedAgent = async (input) => {
|
|
4500
4645
|
if (assignment.agent.runtime === 'CODEX') {
|
|
4501
4646
|
return runCodex({
|
|
@@ -4518,9 +4663,7 @@ async function startWorker(options = {}) {
|
|
|
4518
4663
|
onTranscriptChunks: async (chunks) => {
|
|
4519
4664
|
await appendTranscriptChunks(chunks);
|
|
4520
4665
|
},
|
|
4521
|
-
onChild:
|
|
4522
|
-
activeTerminators.set(task.id, controls.terminate);
|
|
4523
|
-
},
|
|
4666
|
+
onChild: handleAgentChild,
|
|
4524
4667
|
cleanRoom,
|
|
4525
4668
|
timeoutMs: input.timeoutMs,
|
|
4526
4669
|
resumeSessionId: input.resumeSessionId,
|
|
@@ -4548,9 +4691,7 @@ async function startWorker(options = {}) {
|
|
|
4548
4691
|
onTranscriptChunks: async (chunks) => {
|
|
4549
4692
|
await appendTranscriptChunks(chunks);
|
|
4550
4693
|
},
|
|
4551
|
-
onChild:
|
|
4552
|
-
activeTerminators.set(task.id, controls.terminate);
|
|
4553
|
-
},
|
|
4694
|
+
onChild: handleAgentChild,
|
|
4554
4695
|
cleanRoom,
|
|
4555
4696
|
timeoutMs: input.timeoutMs,
|
|
4556
4697
|
resumeSessionId: input.resumeSessionId,
|
|
@@ -4560,7 +4701,6 @@ async function startWorker(options = {}) {
|
|
|
4560
4701
|
await client.workerRecordAgentTaskAttemptUnavailable(task.id, {
|
|
4561
4702
|
workerKey,
|
|
4562
4703
|
leaseToken,
|
|
4563
|
-
attemptIndex: 0,
|
|
4564
4704
|
reason: 'unavailable_runtime',
|
|
4565
4705
|
message: 'Cursor Composer noninteractive worker execution is not configured on this worker.',
|
|
4566
4706
|
});
|
|
@@ -4686,6 +4826,7 @@ async function startWorker(options = {}) {
|
|
|
4686
4826
|
model: assignment.agent.model,
|
|
4687
4827
|
reasoningEffort,
|
|
4688
4828
|
});
|
|
4829
|
+
const availabilityReason = classifyAgentAvailabilityFailureReason(failureCode);
|
|
4689
4830
|
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4690
4831
|
workerKey,
|
|
4691
4832
|
leaseToken,
|
|
@@ -4695,14 +4836,32 @@ async function startWorker(options = {}) {
|
|
|
4695
4836
|
pct: null,
|
|
4696
4837
|
payload: { error: failureCode },
|
|
4697
4838
|
});
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4839
|
+
if (availabilityReason) {
|
|
4840
|
+
retainWorkspace = false;
|
|
4841
|
+
await reportTelemetry('FAILED').catch((error) => {
|
|
4842
|
+
console.error(`Agent task ${task.id} telemetry failed before provider handoff: ${error instanceof Error ? error.message : String(error)}`);
|
|
4843
|
+
});
|
|
4844
|
+
const unavailable = await client.workerRecordAgentTaskAttemptUnavailable(task.id, {
|
|
4845
|
+
workerKey,
|
|
4846
|
+
leaseToken,
|
|
4847
|
+
reason: availabilityReason,
|
|
4848
|
+
message: failureCode,
|
|
4849
|
+
});
|
|
4850
|
+
fenced = true;
|
|
4851
|
+
console.error(unavailable.requeued
|
|
4852
|
+
? `Agent task ${task.id} reported ${availabilityReason}; the server queued the next approved attempt.`
|
|
4853
|
+
: `Agent task ${task.id} reported ${availabilityReason}; no approved attempts remain.`);
|
|
4854
|
+
}
|
|
4855
|
+
else {
|
|
4856
|
+
await failTaskWithRetry(client, task.id, {
|
|
4857
|
+
workerKey,
|
|
4858
|
+
leaseToken,
|
|
4859
|
+
error: failureCode,
|
|
4860
|
+
terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(failureCode, 'agent'),
|
|
4861
|
+
result: agentRunResult,
|
|
4862
|
+
});
|
|
4863
|
+
await reportTelemetry('FAILED');
|
|
4864
|
+
}
|
|
4706
4865
|
}
|
|
4707
4866
|
}
|
|
4708
4867
|
else {
|
|
@@ -5114,7 +5273,7 @@ async function reportTask(options) {
|
|
|
5114
5273
|
const done = options.done?.trim() || null;
|
|
5115
5274
|
const current = options.current?.trim() || null;
|
|
5116
5275
|
if (!message && !done && !current) {
|
|
5117
|
-
(0, messages_1.printErrorWithHelp)('Task report requires a message, done, or current value.', ['Example: playdrop task report --phase
|
|
5276
|
+
(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
5277
|
node_process_1.default.exitCode = 1;
|
|
5119
5278
|
return;
|
|
5120
5279
|
}
|
|
@@ -5150,6 +5309,128 @@ function showTaskContext() {
|
|
|
5150
5309
|
},
|
|
5151
5310
|
}, null, 2));
|
|
5152
5311
|
}
|
|
5312
|
+
function readWorkerTaskMaterialReceipts(workspaceDir) {
|
|
5313
|
+
const receiptPath = workerTaskMaterialsPath(workspaceDir);
|
|
5314
|
+
if (!(0, node_fs_1.existsSync)(receiptPath)) {
|
|
5315
|
+
return [];
|
|
5316
|
+
}
|
|
5317
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(receiptPath, 'utf8'));
|
|
5318
|
+
if (!Array.isArray(parsed)) {
|
|
5319
|
+
throw new Error('task_material_receipts_invalid');
|
|
5320
|
+
}
|
|
5321
|
+
return parsed.map((entry) => {
|
|
5322
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
5323
|
+
throw new Error('task_material_receipts_invalid');
|
|
5324
|
+
}
|
|
5325
|
+
const candidate = entry;
|
|
5326
|
+
const filePath = typeof candidate.filePath === 'string' ? candidate.filePath.trim() : '';
|
|
5327
|
+
const sha256 = typeof candidate.sha256 === 'string' ? candidate.sha256.trim() : '';
|
|
5328
|
+
const presentation = candidate.presentation;
|
|
5329
|
+
const title = typeof candidate.title === 'string' ? candidate.title.trim() : '';
|
|
5330
|
+
if (!node_path_1.default.isAbsolute(filePath)
|
|
5331
|
+
|| !/^[a-f0-9]{64}$/.test(sha256)
|
|
5332
|
+
|| (presentation !== 'frame' && presentation !== 'asset')
|
|
5333
|
+
|| !title) {
|
|
5334
|
+
throw new Error('task_material_receipts_invalid');
|
|
5335
|
+
}
|
|
5336
|
+
return { filePath, sha256, presentation, title };
|
|
5337
|
+
});
|
|
5338
|
+
}
|
|
5339
|
+
function recordWorkerTaskMaterialReceipt(workspaceDir, receipt) {
|
|
5340
|
+
const receipts = readWorkerTaskMaterialReceipts(workspaceDir);
|
|
5341
|
+
const retained = receipts.filter((entry) => (entry.filePath !== receipt.filePath
|
|
5342
|
+
|| entry.presentation !== receipt.presentation));
|
|
5343
|
+
retained.push(receipt);
|
|
5344
|
+
(0, node_fs_1.writeFileSync)(workerTaskMaterialsPath(workspaceDir), `${JSON.stringify(retained, null, 2)}\n`, 'utf8');
|
|
5345
|
+
}
|
|
5346
|
+
async function assertTaskAssetMaterialIsTransparentPng(filePath) {
|
|
5347
|
+
let metadata;
|
|
5348
|
+
let stats;
|
|
5349
|
+
try {
|
|
5350
|
+
metadata = await (0, sharp_1.default)(filePath, { failOn: 'error' }).metadata();
|
|
5351
|
+
stats = await (0, sharp_1.default)(filePath, { failOn: 'error' }).stats();
|
|
5352
|
+
}
|
|
5353
|
+
catch {
|
|
5354
|
+
throw new Error(`task_material_asset_decode_failed:${filePath}`);
|
|
5355
|
+
}
|
|
5356
|
+
if (metadata.format !== 'png') {
|
|
5357
|
+
throw new Error(`task_material_asset_must_be_png:${filePath}`);
|
|
5358
|
+
}
|
|
5359
|
+
if (metadata.hasAlpha !== true || stats.isOpaque) {
|
|
5360
|
+
throw new Error(`task_material_asset_background_not_removed:${filePath}`);
|
|
5361
|
+
}
|
|
5362
|
+
}
|
|
5363
|
+
async function materialTask(options) {
|
|
5364
|
+
try {
|
|
5365
|
+
const fileOption = options.file?.trim() ?? '';
|
|
5366
|
+
const title = options.title?.trim() ?? '';
|
|
5367
|
+
if (!fileOption || !title || title.length > 60 || title.split(/\s+/).length > 3) {
|
|
5368
|
+
throw new Error('task_material_file_and_title_required');
|
|
5369
|
+
}
|
|
5370
|
+
const filePath = node_path_1.default.resolve(fileOption);
|
|
5371
|
+
if (!(0, node_fs_1.existsSync)(filePath) || !(0, node_fs_1.statSync)(filePath).isFile()) {
|
|
5372
|
+
throw new Error(`task_material_file_not_found:${fileOption}`);
|
|
5373
|
+
}
|
|
5374
|
+
const posterOption = options.poster?.trim() ?? '';
|
|
5375
|
+
const posterPath = posterOption ? node_path_1.default.resolve(posterOption) : null;
|
|
5376
|
+
if (posterPath && (!(0, node_fs_1.existsSync)(posterPath) || !(0, node_fs_1.statSync)(posterPath).isFile())) {
|
|
5377
|
+
throw new Error(`task_material_poster_not_found:${posterOption}`);
|
|
5378
|
+
}
|
|
5379
|
+
const presentation = options.asset ? 'asset' : 'frame';
|
|
5380
|
+
if (presentation === 'asset') {
|
|
5381
|
+
if (posterPath) {
|
|
5382
|
+
throw new Error('task_material_asset_poster_not_allowed');
|
|
5383
|
+
}
|
|
5384
|
+
await assertTaskAssetMaterialIsTransparentPng(filePath);
|
|
5385
|
+
}
|
|
5386
|
+
const taskContext = readTaskContextFile();
|
|
5387
|
+
const ctx = await resolveTaskCommandContext('task material', options.env, taskContext);
|
|
5388
|
+
if (!ctx) {
|
|
5389
|
+
throw new Error('task_material_context_unavailable');
|
|
5390
|
+
}
|
|
5391
|
+
const fileBuffer = (0, node_fs_1.readFileSync)(filePath);
|
|
5392
|
+
const posterBuffer = posterPath ? (0, node_fs_1.readFileSync)(posterPath) : null;
|
|
5393
|
+
const clientKey = (0, node_crypto_1.createHash)('sha256')
|
|
5394
|
+
.update([
|
|
5395
|
+
String(taskContext.taskId),
|
|
5396
|
+
String(taskContext.attempt),
|
|
5397
|
+
node_path_1.default.relative(node_process_1.default.cwd(), filePath),
|
|
5398
|
+
(0, node_crypto_1.createHash)('sha256').update(fileBuffer).digest('hex'),
|
|
5399
|
+
title,
|
|
5400
|
+
posterBuffer ? (0, node_crypto_1.createHash)('sha256').update(posterBuffer).digest('hex') : '',
|
|
5401
|
+
presentation,
|
|
5402
|
+
].join(':'))
|
|
5403
|
+
.digest('hex')
|
|
5404
|
+
.slice(0, 40);
|
|
5405
|
+
await ctx.client.workerUploadAgentTaskMaterial(taskContext.taskId, {
|
|
5406
|
+
file: new Blob([new Uint8Array(fileBuffer)]),
|
|
5407
|
+
fileName: node_path_1.default.basename(filePath),
|
|
5408
|
+
title,
|
|
5409
|
+
clientKey,
|
|
5410
|
+
presentation,
|
|
5411
|
+
phase: options.phase?.trim() || undefined,
|
|
5412
|
+
taskToken: taskContext.taskToken,
|
|
5413
|
+
...(posterBuffer && posterPath
|
|
5414
|
+
? {
|
|
5415
|
+
poster: new Blob([new Uint8Array(posterBuffer)]),
|
|
5416
|
+
posterFileName: node_path_1.default.basename(posterPath),
|
|
5417
|
+
}
|
|
5418
|
+
: {}),
|
|
5419
|
+
});
|
|
5420
|
+
recordWorkerTaskMaterialReceipt(resolveTaskWorkspaceDir(), {
|
|
5421
|
+
filePath,
|
|
5422
|
+
sha256: (0, node_crypto_1.createHash)('sha256').update(fileBuffer).digest('hex'),
|
|
5423
|
+
presentation,
|
|
5424
|
+
title,
|
|
5425
|
+
});
|
|
5426
|
+
(0, output_1.printSuccess)(`Task material shared: ${title}`);
|
|
5427
|
+
}
|
|
5428
|
+
catch (error) {
|
|
5429
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5430
|
+
console.warn(`Warning: task material was not shared: ${message}`);
|
|
5431
|
+
node_process_1.default.exitCode = 0;
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5153
5434
|
async function reportCatalogueTask(options) {
|
|
5154
5435
|
const message = options.message?.trim();
|
|
5155
5436
|
if (!message) {
|
|
@@ -5216,6 +5497,38 @@ async function uploadTask(options = {}) {
|
|
|
5216
5497
|
}
|
|
5217
5498
|
const uploadKind = taskContext.kind;
|
|
5218
5499
|
const workspaceDir = resolveTaskWorkspaceDir();
|
|
5500
|
+
if (options.preflightOnly) {
|
|
5501
|
+
const ctx = await resolveTaskCommandContext('task upload --preflight-only', options.env, taskContext);
|
|
5502
|
+
if (!ctx) {
|
|
5503
|
+
return;
|
|
5504
|
+
}
|
|
5505
|
+
const projectDir = discoverWorkerProjectRoot(node_process_1.default.cwd());
|
|
5506
|
+
const result = await (0, upload_1.preflightWorkerAppProject)({
|
|
5507
|
+
client: ctx.client,
|
|
5508
|
+
taskId: taskContext.taskId,
|
|
5509
|
+
taskToken: taskContext.taskToken,
|
|
5510
|
+
kind: uploadKind,
|
|
5511
|
+
executionTarget: taskContext.target,
|
|
5512
|
+
expectedAppName: taskContext.outputAppName ?? undefined,
|
|
5513
|
+
expectedDisplayName: taskContext.outputDisplayName,
|
|
5514
|
+
expectedSubtitle: taskContext.outputSubtitle,
|
|
5515
|
+
expectedPrimarySurface: taskContext.outputPrimarySurface,
|
|
5516
|
+
remixSourceRef: taskContext.remixSourceRef ?? null,
|
|
5517
|
+
playdropAssetRequirement: (0, upload_1.resolveWorkerPlaydropAssetRequirement)(taskContext.creatorRequest),
|
|
5518
|
+
creatorRequest: taskContext.creatorRequest,
|
|
5519
|
+
projectDir,
|
|
5520
|
+
creatorUsername: taskContext.creatorUsername,
|
|
5521
|
+
apiBase: ctx.envConfig.apiBase,
|
|
5522
|
+
webBase: ctx.envConfig.webBase ?? null,
|
|
5523
|
+
token: ctx.token,
|
|
5524
|
+
user: ctx.user,
|
|
5525
|
+
});
|
|
5526
|
+
for (const warning of result.warnings) {
|
|
5527
|
+
console.error(`Preflight warning: ${warning}`);
|
|
5528
|
+
}
|
|
5529
|
+
(0, output_1.printSuccess)(`Task upload preflight passed for ${result.creatorUsername}/${result.appName}. Final capture can start.`);
|
|
5530
|
+
return;
|
|
5531
|
+
}
|
|
5219
5532
|
if ((0, node_fs_1.existsSync)(workerTaskUploadResultPath(workspaceDir))) {
|
|
5220
5533
|
const existingResult = readTaskUploadResultFile(workspaceDir);
|
|
5221
5534
|
assertTaskUploadResultMatchesContext({
|
|
@@ -5238,6 +5551,9 @@ async function uploadTask(options = {}) {
|
|
|
5238
5551
|
kind: uploadKind,
|
|
5239
5552
|
executionTarget: taskContext.target,
|
|
5240
5553
|
expectedAppName: taskContext.outputAppName ?? undefined,
|
|
5554
|
+
expectedDisplayName: taskContext.outputDisplayName,
|
|
5555
|
+
expectedSubtitle: taskContext.outputSubtitle,
|
|
5556
|
+
expectedPrimarySurface: taskContext.outputPrimarySurface,
|
|
5241
5557
|
remixSourceRef: taskContext.remixSourceRef ?? null,
|
|
5242
5558
|
playdropAssetRequirement: (0, upload_1.resolveWorkerPlaydropAssetRequirement)(taskContext.creatorRequest),
|
|
5243
5559
|
creatorRequest: taskContext.creatorRequest,
|
|
@@ -5271,26 +5587,55 @@ async function claimSlugTask(options) {
|
|
|
5271
5587
|
}
|
|
5272
5588
|
const appName = normalizeTaskClaimSlugAppName(options.appName);
|
|
5273
5589
|
const displayName = normalizeTaskClaimSlugDisplayName(options.displayName);
|
|
5590
|
+
const subtitle = normalizeTaskClaimSlugSubtitle(options.subtitle);
|
|
5591
|
+
const primarySurface = normalizeTaskClaimSlugPrimarySurface(options.primarySurface);
|
|
5274
5592
|
const ctx = await resolveTaskCommandContext('task claim-slug', options.env, taskContext);
|
|
5275
5593
|
if (!ctx) {
|
|
5276
5594
|
return;
|
|
5277
5595
|
}
|
|
5278
|
-
const
|
|
5596
|
+
const request = {
|
|
5279
5597
|
appName,
|
|
5280
5598
|
displayName,
|
|
5281
|
-
|
|
5599
|
+
...(subtitle ? { subtitle } : {}),
|
|
5600
|
+
...(primarySurface ? { primarySurface } : {}),
|
|
5601
|
+
};
|
|
5602
|
+
const response = await ctx.client.workerClaimAgentTaskSlug(taskContext.taskId, request);
|
|
5282
5603
|
const claimedAppName = typeof response.appName === 'string' && response.appName.trim()
|
|
5283
5604
|
? response.appName.trim()
|
|
5284
5605
|
: '';
|
|
5285
5606
|
if (claimedAppName !== appName) {
|
|
5286
5607
|
throw new Error('task_claim_slug_response_mismatch');
|
|
5287
5608
|
}
|
|
5609
|
+
const hasResponseDisplayName = Object.prototype.hasOwnProperty.call(response, 'displayName');
|
|
5610
|
+
const responseDisplayName = typeof response.displayName === 'string' && response.displayName.trim()
|
|
5611
|
+
? response.displayName.trim()
|
|
5612
|
+
: '';
|
|
5613
|
+
if (hasResponseDisplayName && responseDisplayName !== displayName) {
|
|
5614
|
+
throw new Error('task_claim_identity_response_mismatch');
|
|
5615
|
+
}
|
|
5616
|
+
const hasResponseSubtitle = Object.prototype.hasOwnProperty.call(response, 'subtitle');
|
|
5617
|
+
const responseSubtitle = typeof response.subtitle === 'string' && response.subtitle.trim()
|
|
5618
|
+
? response.subtitle.trim()
|
|
5619
|
+
: null;
|
|
5620
|
+
const hasResponsePrimarySurface = Object.prototype.hasOwnProperty.call(response, 'primarySurface');
|
|
5621
|
+
const responsePrimarySurface = normalizeWorkerInstrumentSurface(response.primarySurface);
|
|
5622
|
+
if ((subtitle && hasResponseSubtitle && responseSubtitle !== subtitle)
|
|
5623
|
+
|| (primarySurface && hasResponsePrimarySurface && responsePrimarySurface !== primarySurface)) {
|
|
5624
|
+
throw new Error('task_claim_identity_response_mismatch');
|
|
5625
|
+
}
|
|
5626
|
+
const claimedSubtitle = subtitle ?? responseSubtitle ?? taskContext.outputSubtitle;
|
|
5627
|
+
const claimedPrimarySurface = primarySurface ?? responsePrimarySurface ?? taskContext.outputPrimarySurface;
|
|
5288
5628
|
const workspaceDir = resolveTaskWorkspaceDir();
|
|
5289
5629
|
await writeWorkerTaskContextFile(workspaceDir, {
|
|
5290
5630
|
...taskContext,
|
|
5291
5631
|
outputAppName: claimedAppName,
|
|
5632
|
+
outputDisplayName: displayName,
|
|
5633
|
+
outputSubtitle: claimedSubtitle,
|
|
5634
|
+
outputPrimarySurface: claimedPrimarySurface,
|
|
5292
5635
|
});
|
|
5293
|
-
(0, output_1.printSuccess)(
|
|
5636
|
+
(0, output_1.printSuccess)(claimedPrimarySurface
|
|
5637
|
+
? `Claimed "${displayName}" (${claimedAppName}) for ${claimedPrimarySurface}.`
|
|
5638
|
+
: `Claimed app slug "${claimedAppName}" for ${displayName}.`);
|
|
5294
5639
|
}
|
|
5295
5640
|
async function completeTask(options) {
|
|
5296
5641
|
const taskContext = readTaskContextFile();
|
|
@@ -5298,9 +5643,12 @@ async function completeTask(options) {
|
|
|
5298
5643
|
throw new Error('task_done_not_allowed_for_game_review');
|
|
5299
5644
|
}
|
|
5300
5645
|
const summary = typeof options.summary === 'string' ? options.summary.trim() : '';
|
|
5301
|
-
if (!summary ||
|
|
5646
|
+
if (!summary || /[\r\n]/.test(summary)) {
|
|
5302
5647
|
throw new Error('task_done_summary_required');
|
|
5303
5648
|
}
|
|
5649
|
+
if (summary.length > 200) {
|
|
5650
|
+
throw new Error('task_done_summary_too_long:max_200_characters');
|
|
5651
|
+
}
|
|
5304
5652
|
const ctx = await resolveTaskCommandContext('task done', options.env, taskContext);
|
|
5305
5653
|
if (!ctx) {
|
|
5306
5654
|
return;
|