@playdrop/playdrop-cli 0.12.31 → 0.12.33
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/apiClient.js +16 -1
- package/dist/commands/upload.js +2 -0
- package/dist/commands/worker/runtime.d.ts +2 -0
- package/dist/commands/worker/runtime.js +14 -10
- package/dist/commands/worker.d.ts +23 -30
- package/dist/commands/worker.js +639 -656
- package/dist/index.js +6 -0
- package/node_modules/@playdrop/api-client/dist/client.d.ts +2 -1
- 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 +2 -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 +11 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +3 -2
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +5 -0
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/types/dist/agent-task-terminal.d.ts +5 -0
- package/node_modules/@playdrop/types/dist/agent-task-terminal.d.ts.map +1 -0
- package/node_modules/@playdrop/types/dist/agent-task-terminal.js +117 -0
- package/node_modules/@playdrop/types/dist/api.d.ts +100 -102
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +1 -1
- package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
- package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/index.js +3 -1
- package/package.json +1 -1
package/dist/commands/worker.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.WORKER_TASK_WORKSPACE_RETENTION = exports.WORKER_CONTEXT_COMMAND_NOT_ALL
|
|
|
7
7
|
exports.allocateWorkerDevPort = allocateWorkerDevPort;
|
|
8
8
|
exports.isWorkerContextCommandAllowed = isWorkerContextCommandAllowed;
|
|
9
9
|
exports.resolveWorkerExecutionTargetFromRole = resolveWorkerExecutionTargetFromRole;
|
|
10
|
-
exports.
|
|
10
|
+
exports.normalizeWorkerTaskAssignmentV2 = normalizeWorkerTaskAssignmentV2;
|
|
11
11
|
exports.nextClaimBackoffMs = nextClaimBackoffMs;
|
|
12
12
|
exports.claimBackoffDelayMs = claimBackoffDelayMs;
|
|
13
13
|
exports.resolveWorkerHomeDir = resolveWorkerHomeDir;
|
|
@@ -16,9 +16,7 @@ exports.hasAgentTaskUploadedArtifact = hasAgentTaskUploadedArtifact;
|
|
|
16
16
|
exports.appendWorkerTranscriptChunks = appendWorkerTranscriptChunks;
|
|
17
17
|
exports.readTaskNextStepsFile = readTaskNextStepsFile;
|
|
18
18
|
exports.assertTaskUploadResultMatchesContext = assertTaskUploadResultMatchesContext;
|
|
19
|
-
exports.
|
|
20
|
-
exports.stagePlaydropPluginReferences = stagePlaydropPluginReferences;
|
|
21
|
-
exports.stageCodexTaskSkills = stageCodexTaskSkills;
|
|
19
|
+
exports.stageAssignmentWorkspaceV2 = stageAssignmentWorkspaceV2;
|
|
22
20
|
exports.assertAssignmentPluginBundleExtractionBounds = assertAssignmentPluginBundleExtractionBounds;
|
|
23
21
|
exports.stageAssignmentPluginBundle = stageAssignmentPluginBundle;
|
|
24
22
|
exports.resolveDevPluginWorkingTree = resolveDevPluginWorkingTree;
|
|
@@ -35,12 +33,11 @@ exports.resolvePlaywrightBrowsersPathFromExecutable = resolvePlaywrightBrowsersP
|
|
|
35
33
|
exports.loadWorkerEnvFile = loadWorkerEnvFile;
|
|
36
34
|
exports.buildWorkerCapabilities = buildWorkerCapabilities;
|
|
37
35
|
exports.buildWorkerClaimBody = buildWorkerClaimBody;
|
|
38
|
-
exports.resolveWorkerBaseSource = resolveWorkerBaseSource;
|
|
39
36
|
exports.extractZipArchive = extractZipArchive;
|
|
40
|
-
exports.readWorkerProjectVersion = readWorkerProjectVersion;
|
|
41
37
|
exports.buildCataloguePreviewPayload = buildCataloguePreviewPayload;
|
|
42
|
-
exports.
|
|
43
|
-
exports.
|
|
38
|
+
exports.removeStaleWorkerControlFiles = removeStaleWorkerControlFiles;
|
|
39
|
+
exports.resolveCodexModel = resolveCodexModel;
|
|
40
|
+
exports.resolveClaudeModel = resolveClaudeModel;
|
|
44
41
|
exports.prepareCodexRunHomes = prepareCodexRunHomes;
|
|
45
42
|
exports.prepareClaudeRunConfig = prepareClaudeRunConfig;
|
|
46
43
|
exports.removeAgentRunCredentials = removeAgentRunCredentials;
|
|
@@ -74,6 +71,7 @@ exports.instrumentTaskChromeTitleMatchers = instrumentTaskChromeTitleMatchers;
|
|
|
74
71
|
exports.enforceInstrumentBrowserHygiene = enforceInstrumentBrowserHygiene;
|
|
75
72
|
exports.startWorker = startWorker;
|
|
76
73
|
exports.reportTask = reportTask;
|
|
74
|
+
exports.showTaskContext = showTaskContext;
|
|
77
75
|
exports.reportCatalogueTask = reportCatalogueTask;
|
|
78
76
|
exports.uploadTask = uploadTask;
|
|
79
77
|
exports.claimSlugTask = claimSlugTask;
|
|
@@ -95,6 +93,7 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
95
93
|
const node_path_1 = __importDefault(require("node:path"));
|
|
96
94
|
const node_process_1 = __importDefault(require("node:process"));
|
|
97
95
|
const node_util_1 = require("node:util");
|
|
96
|
+
const apiClient_1 = require("../apiClient");
|
|
98
97
|
const clientInfo_1 = require("../clientInfo");
|
|
99
98
|
const commandContext_1 = require("../commandContext");
|
|
100
99
|
const config_1 = require("../config");
|
|
@@ -134,15 +133,13 @@ const CLAIM_BACKOFF_JITTER_MS = 500;
|
|
|
134
133
|
const FAIL_REPORT_RETRY_DELAY_MS = 2000;
|
|
135
134
|
const WORKER_TRANSCRIPT_CHUNK_BATCH_SIZE = 100;
|
|
136
135
|
const SLACK_API_BASE_URL = 'https://slack.com/api';
|
|
137
|
-
const WORKER_WRAPPER_CONTRACT_VERSION =
|
|
136
|
+
const WORKER_WRAPPER_CONTRACT_VERSION = 2;
|
|
138
137
|
const WORKER_SUPPORTED_KINDS = ['NEW_GAME', 'REMIX_GAME', 'GAME_UPDATE', 'GAME_REVIEW', 'GAME_EVAL'];
|
|
139
|
-
const PLAYDROP_PLUGIN_MANIFEST_REQUIRED_STARTUP_KINDS = WORKER_SUPPORTED_KINDS;
|
|
140
138
|
const requireFromWorker = (0, node_module_1.createRequire)(__filename);
|
|
141
|
-
const
|
|
142
|
-
const STAGED_CODEX_SKILL_ROOT = '.agents/skills';
|
|
143
|
-
const PLAYDROP_PLUGIN_STAGING_MANIFEST_PATH = 'playdrop-worker-staging.json';
|
|
139
|
+
const AGENT_BUNDLE_MANIFEST_PATH = 'agent-bundle.json';
|
|
144
140
|
const ASSIGNMENT_PLUGIN_BUNDLE_MAX_FILES = 1000;
|
|
145
141
|
const ASSIGNMENT_PLUGIN_BUNDLE_MAX_UNCOMPRESSED_BYTES = 20 * 1024 * 1024;
|
|
142
|
+
const ASSIGNMENT_WORKSPACE_ARCHIVE_MAX_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;
|
|
146
143
|
const ZIP_CENTRAL_DIRECTORY_SIGNATURE = 0x02014b50;
|
|
147
144
|
const ZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50;
|
|
148
145
|
const ZIP_END_OF_CENTRAL_DIRECTORY_MIN_BYTES = 22;
|
|
@@ -150,6 +147,7 @@ const ZIP_MAX_COMMENT_BYTES = 0xffff;
|
|
|
150
147
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
151
148
|
exports.WORKER_SESSION_EXPIRED_MESSAGE = 'worker session expired: run "playdrop auth login" and start the worker again';
|
|
152
149
|
const WORKER_CONTEXT_ALLOWED_COMMANDS = [
|
|
150
|
+
['task', 'context'],
|
|
153
151
|
['task', 'report'],
|
|
154
152
|
['task', 'report-catalogue'],
|
|
155
153
|
['task', 'claim-slug'],
|
|
@@ -298,105 +296,162 @@ function normalizeAssignmentAgent(value) {
|
|
|
298
296
|
}
|
|
299
297
|
return value;
|
|
300
298
|
}
|
|
301
|
-
function normalizeAssignmentKind(value, task) {
|
|
302
|
-
if (value !== 'NEW_GAME' && value !== 'REMIX_GAME' && value !== 'GAME_UPDATE' && value !== 'GAME_REVIEW' && value !== 'GAME_EVAL') {
|
|
303
|
-
throw new Error('agent_task_assignment_kind_missing');
|
|
304
|
-
}
|
|
305
|
-
if (value !== task.kind) {
|
|
306
|
-
throw new Error('agent_task_assignment_kind_mismatch');
|
|
307
|
-
}
|
|
308
|
-
return value;
|
|
309
|
-
}
|
|
310
299
|
function normalizeAssignmentTarget(value) {
|
|
311
300
|
if (value !== 'FIRST_PARTY' && value !== 'PERSONAL') {
|
|
312
301
|
throw new Error('agent_task_assignment_target_missing');
|
|
313
302
|
}
|
|
314
303
|
return value;
|
|
315
304
|
}
|
|
316
|
-
function
|
|
317
|
-
|
|
318
|
-
|
|
305
|
+
function normalizeAssignmentContent(input) {
|
|
306
|
+
const relativePath = normalizeAssignmentWorkspaceRelativePath(input.path);
|
|
307
|
+
const contentBase64 = typeof input.contentBase64 === 'string' ? input.contentBase64.trim() : '';
|
|
308
|
+
const sha256 = typeof input.sha256 === 'string' ? input.sha256.trim() : '';
|
|
309
|
+
const sizeBytes = Number(input.sizeBytes);
|
|
310
|
+
if (!contentBase64 || !/^[A-Za-z0-9+/]+={0,2}$/.test(contentBase64)) {
|
|
311
|
+
throw new Error(`${input.code}_content_invalid`);
|
|
319
312
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
throw new Error('agent_task_claim_missing_task_assignment');
|
|
313
|
+
if (!/^[a-f0-9]{64}$/.test(sha256) || !Number.isInteger(sizeBytes) || sizeBytes <= 0) {
|
|
314
|
+
throw new Error(`${input.code}_metadata_invalid`);
|
|
323
315
|
}
|
|
324
|
-
const
|
|
325
|
-
if (
|
|
326
|
-
throw new Error(
|
|
327
|
-
}
|
|
328
|
-
const taskId = parsePositiveInteger(assignment.taskId, 'agent_task_assignment_task_id_missing');
|
|
329
|
-
if (taskId !== claim.task.id) {
|
|
330
|
-
throw new Error('agent_task_assignment_task_id_mismatch');
|
|
316
|
+
const buffer = Buffer.from(contentBase64, 'base64');
|
|
317
|
+
if (buffer.length !== sizeBytes) {
|
|
318
|
+
throw new Error(`${input.code}_size_mismatch`);
|
|
331
319
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
throw new Error('agent_task_assignment_token_missing');
|
|
320
|
+
if ((0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex') !== sha256) {
|
|
321
|
+
throw new Error(`${input.code}_sha_mismatch`);
|
|
335
322
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
? assignment.workspace
|
|
342
|
-
: null;
|
|
343
|
-
if (!workspace) {
|
|
344
|
-
throw new Error('agent_task_assignment_workspace_missing');
|
|
323
|
+
return { path: relativePath, contentBase64, sha256, sizeBytes, buffer };
|
|
324
|
+
}
|
|
325
|
+
function normalizeWorkerTaskAssignmentV2(claim) {
|
|
326
|
+
if (claim.protocolVersion !== 2) {
|
|
327
|
+
throw new Error('agent_task_claim_protocol_version_mismatch');
|
|
345
328
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
: null;
|
|
349
|
-
if (!inputs) {
|
|
350
|
-
throw new Error('agent_task_assignment_inputs_missing');
|
|
329
|
+
if (claim.action !== 'run') {
|
|
330
|
+
return null;
|
|
351
331
|
}
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
332
|
+
const assignment = claim.assignment;
|
|
333
|
+
const taskId = parsePositiveInteger(assignment.task.id, 'agent_task_assignment_task_id_missing');
|
|
334
|
+
const taskToken = typeof assignment.task.token === 'string' ? assignment.task.token.trim() : '';
|
|
335
|
+
const taskType = typeof assignment.task.type === 'string' ? assignment.task.type.trim() : '';
|
|
336
|
+
const attempt = parsePositiveInteger(assignment.task.attempt, 'agent_task_assignment_attempt_missing');
|
|
337
|
+
const prompt = typeof assignment.request.prompt === 'string' ? assignment.request.prompt : '';
|
|
338
|
+
const model = typeof assignment.agent.model === 'string' ? assignment.agent.model.trim() : '';
|
|
339
|
+
if (!taskToken || !taskType || !prompt.trim() || !model) {
|
|
340
|
+
throw new Error('agent_task_assignment_invalid');
|
|
341
|
+
}
|
|
342
|
+
const workspacePaths = new Set();
|
|
343
|
+
const files = assignment.workspace.files.map((file) => {
|
|
344
|
+
const normalized = normalizeAssignmentContent({
|
|
345
|
+
path: file.path,
|
|
346
|
+
contentBase64: file.contentBase64,
|
|
347
|
+
sha256: file.sha256,
|
|
348
|
+
sizeBytes: file.sizeBytes,
|
|
349
|
+
code: 'agent_task_assignment_workspace_file',
|
|
350
|
+
});
|
|
351
|
+
const contentType = typeof file.contentType === 'string' ? file.contentType.trim() : '';
|
|
352
|
+
if (!contentType) {
|
|
353
|
+
throw new Error('agent_task_assignment_workspace_file_content_type_missing');
|
|
354
|
+
}
|
|
355
|
+
if (workspacePaths.has(normalized.path)) {
|
|
356
|
+
throw new Error('agent_task_assignment_workspace_path_duplicate');
|
|
357
|
+
}
|
|
358
|
+
workspacePaths.add(normalized.path);
|
|
359
|
+
return {
|
|
360
|
+
path: normalized.path,
|
|
361
|
+
sha256: normalized.sha256,
|
|
362
|
+
sizeBytes: normalized.sizeBytes,
|
|
363
|
+
contentType,
|
|
364
|
+
contentBase64: normalized.contentBase64,
|
|
365
|
+
};
|
|
366
|
+
});
|
|
367
|
+
const archives = assignment.workspace.archives.map((archive) => {
|
|
368
|
+
if (archive.format !== 'zip') {
|
|
369
|
+
throw new Error('agent_task_assignment_workspace_archive_format_invalid');
|
|
370
|
+
}
|
|
371
|
+
const normalized = normalizeAssignmentContent({
|
|
372
|
+
path: archive.path,
|
|
373
|
+
contentBase64: archive.contentBase64,
|
|
374
|
+
sha256: archive.sha256,
|
|
375
|
+
sizeBytes: archive.sizeBytes,
|
|
376
|
+
code: 'agent_task_assignment_workspace_archive',
|
|
377
|
+
});
|
|
378
|
+
if (workspacePaths.has(normalized.path)) {
|
|
379
|
+
throw new Error('agent_task_assignment_workspace_path_duplicate');
|
|
380
|
+
}
|
|
381
|
+
workspacePaths.add(normalized.path);
|
|
382
|
+
return {
|
|
383
|
+
path: normalized.path,
|
|
384
|
+
format: 'zip',
|
|
385
|
+
sha256: normalized.sha256,
|
|
386
|
+
sizeBytes: normalized.sizeBytes,
|
|
387
|
+
contentBase64: normalized.contentBase64,
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
const fileByPath = new Map(files.map((file) => [file.path, file]));
|
|
391
|
+
const attachmentIds = new Set();
|
|
392
|
+
const attachments = assignment.request.attachments.map((attachment) => {
|
|
393
|
+
const id = typeof attachment.id === 'string' ? attachment.id.trim() : '';
|
|
394
|
+
const attachmentPath = normalizeAssignmentWorkspaceRelativePath(attachment.path);
|
|
395
|
+
const fileName = typeof attachment.fileName === 'string' ? attachment.fileName.trim() : '';
|
|
396
|
+
const contentType = typeof attachment.contentType === 'string' ? attachment.contentType.trim() : '';
|
|
397
|
+
const sha256 = typeof attachment.sha256 === 'string' ? attachment.sha256.trim() : '';
|
|
398
|
+
const sizeBytes = Number(attachment.sizeBytes);
|
|
399
|
+
const file = fileByPath.get(attachmentPath);
|
|
400
|
+
if (!id || attachmentIds.has(id) || !fileName || !contentType || !file) {
|
|
401
|
+
throw new Error('agent_task_assignment_attachment_invalid');
|
|
402
|
+
}
|
|
403
|
+
if (sha256 !== file.sha256 || sizeBytes !== file.sizeBytes || contentType !== file.contentType) {
|
|
404
|
+
throw new Error('agent_task_assignment_attachment_metadata_mismatch');
|
|
405
|
+
}
|
|
406
|
+
attachmentIds.add(id);
|
|
407
|
+
return {
|
|
408
|
+
id,
|
|
409
|
+
path: attachmentPath,
|
|
410
|
+
fileName,
|
|
411
|
+
contentType,
|
|
412
|
+
sizeBytes,
|
|
413
|
+
sha256,
|
|
414
|
+
description: typeof attachment.description === 'string' ? attachment.description : null,
|
|
415
|
+
};
|
|
416
|
+
});
|
|
417
|
+
for (const attachment of attachments) {
|
|
418
|
+
if (!fileByPath.has(attachment.path)) {
|
|
419
|
+
throw new Error('agent_task_assignment_attachment_file_missing');
|
|
420
|
+
}
|
|
357
421
|
}
|
|
358
|
-
const
|
|
359
|
-
if (
|
|
360
|
-
throw new Error('
|
|
422
|
+
const browser = assignment.agent.browser;
|
|
423
|
+
if (browser !== 'NONE' && browser !== 'CHROME' && browser !== 'PLAYWRIGHT') {
|
|
424
|
+
throw new Error('agent_task_assignment_browser_invalid');
|
|
361
425
|
}
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if (!pluginBundle) {
|
|
366
|
-
throw new Error('agent_task_assignment_plugin_bundle_missing');
|
|
426
|
+
const reasoningEffort = assignment.agent.reasoningEffort;
|
|
427
|
+
if (reasoningEffort !== null && typeof reasoningEffort !== 'string') {
|
|
428
|
+
throw new Error('agent_task_assignment_reasoning_effort_invalid');
|
|
367
429
|
}
|
|
368
430
|
return {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
431
|
+
task: {
|
|
432
|
+
id: taskId,
|
|
433
|
+
token: taskToken,
|
|
434
|
+
type: taskType,
|
|
435
|
+
executionTarget: normalizeAssignmentTarget(assignment.task.executionTarget),
|
|
436
|
+
attempt,
|
|
437
|
+
metadata: assignment.task.metadata,
|
|
438
|
+
},
|
|
375
439
|
workspace: {
|
|
376
|
-
folderName: normalizeAssignmentWorkspaceFolder(workspace.folderName),
|
|
377
|
-
|
|
440
|
+
folderName: normalizeAssignmentWorkspaceFolder(assignment.workspace.folderName),
|
|
441
|
+
archives,
|
|
442
|
+
files,
|
|
378
443
|
},
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
attachments: Array.isArray(inputs.attachments) ? inputs.attachments : [],
|
|
384
|
-
baseSource: inputs.baseSource ?? null,
|
|
385
|
-
remixSource: inputs.remixSource ?? null,
|
|
386
|
-
reviewTarget: inputs.reviewTarget ?? null,
|
|
387
|
-
evalTarget: inputs.evalTarget ?? null,
|
|
388
|
-
allowedTemplateKeys: Array.isArray(inputs.allowedTemplateKeys)
|
|
389
|
-
? Array.from(new Set(inputs.allowedTemplateKeys.map((value) => (typeof value === 'string' ? value.trim() : '')).filter(Boolean)))
|
|
390
|
-
: [],
|
|
444
|
+
plugin: assignment.plugin,
|
|
445
|
+
request: {
|
|
446
|
+
prompt,
|
|
447
|
+
attachments,
|
|
391
448
|
},
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
releaseNoteRequired: output.releaseNoteRequired !== false,
|
|
449
|
+
agent: {
|
|
450
|
+
runtime: normalizeAssignmentAgent(assignment.agent.runtime),
|
|
451
|
+
model,
|
|
452
|
+
reasoningEffort,
|
|
453
|
+
browser,
|
|
398
454
|
},
|
|
399
|
-
prompt,
|
|
400
455
|
};
|
|
401
456
|
}
|
|
402
457
|
function nextClaimBackoffMs(currentMs) {
|
|
@@ -445,73 +500,55 @@ async function pruneWorkerTaskWorkspaces(incomingWorkspaceDir, retention = expor
|
|
|
445
500
|
}
|
|
446
501
|
return removed;
|
|
447
502
|
}
|
|
448
|
-
function buildWorkerTaskContext(
|
|
449
|
-
const
|
|
450
|
-
if (!
|
|
451
|
-
throw new Error('
|
|
452
|
-
}
|
|
453
|
-
if (task.id !== assignment.taskId) {
|
|
454
|
-
throw new Error('agent_task_context_task_id_mismatch');
|
|
503
|
+
function buildWorkerTaskContext(input) {
|
|
504
|
+
const playdrop = input.metadata.playdrop;
|
|
505
|
+
if (!playdrop || typeof playdrop !== 'object' || Array.isArray(playdrop)) {
|
|
506
|
+
throw new Error('agent_task_context_playdrop_metadata_missing');
|
|
455
507
|
}
|
|
456
|
-
|
|
457
|
-
|
|
508
|
+
const metadata = playdrop;
|
|
509
|
+
const kind = metadata.kind;
|
|
510
|
+
if (kind !== 'NEW_GAME' && kind !== 'REMIX_GAME' && kind !== 'GAME_UPDATE' && kind !== 'GAME_REVIEW' && kind !== 'GAME_EVAL') {
|
|
511
|
+
throw new Error('agent_task_context_kind_missing');
|
|
458
512
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const
|
|
463
|
-
|
|
513
|
+
const creatorUsername = typeof metadata.creatorUsername === 'string'
|
|
514
|
+
? metadata.creatorUsername.trim()
|
|
515
|
+
: '';
|
|
516
|
+
const output = metadata.output && typeof metadata.output === 'object' && !Array.isArray(metadata.output)
|
|
517
|
+
? metadata.output
|
|
518
|
+
: null;
|
|
519
|
+
const outputVersion = typeof output?.version === 'string' ? output.version.trim() : '';
|
|
520
|
+
const outputAppName = typeof output?.appName === 'string' && output.appName.trim()
|
|
521
|
+
? output.appName.trim()
|
|
522
|
+
: null;
|
|
464
523
|
if (!creatorUsername || !outputVersion) {
|
|
465
524
|
throw new Error('agent_task_context_invalid');
|
|
466
525
|
}
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
throw new Error('agent_task_context_token_missing');
|
|
470
|
-
}
|
|
471
|
-
const attempt = Number(task.attempts);
|
|
472
|
-
if (!Number.isInteger(attempt) || attempt <= 0) {
|
|
473
|
-
throw new Error('agent_task_context_attempt_missing');
|
|
474
|
-
}
|
|
475
|
-
const outputAppName = typeof assignment.output.appName === 'string' && assignment.output.appName.trim()
|
|
476
|
-
? assignment.output.appName.trim()
|
|
526
|
+
const remixSource = metadata.remixSource && typeof metadata.remixSource === 'object' && !Array.isArray(metadata.remixSource)
|
|
527
|
+
? metadata.remixSource
|
|
477
528
|
: null;
|
|
478
|
-
const
|
|
479
|
-
?
|
|
529
|
+
const remixSourceRef = typeof remixSource?.ref === 'string' && remixSource.ref.trim()
|
|
530
|
+
? remixSource.ref.trim()
|
|
480
531
|
: null;
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const parsedReviewAppVersionId = Number(reviewTarget?.appVersionId);
|
|
484
|
-
if (!Number.isInteger(parsedReviewAppVersionId) || parsedReviewAppVersionId <= 0) {
|
|
485
|
-
throw new Error('agent_task_context_review_target_missing');
|
|
486
|
-
}
|
|
487
|
-
reviewAppVersionId = parsedReviewAppVersionId;
|
|
488
|
-
}
|
|
489
|
-
const remixSource = assignment.inputs.remixSource && typeof assignment.inputs.remixSource === 'object'
|
|
490
|
-
? assignment.inputs.remixSource
|
|
532
|
+
const reviewTarget = metadata.reviewTarget && typeof metadata.reviewTarget === 'object' && !Array.isArray(metadata.reviewTarget)
|
|
533
|
+
? metadata.reviewTarget
|
|
491
534
|
: null;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
remixSourceRef = parsedRemixSourceRef;
|
|
499
|
-
}
|
|
500
|
-
const allowedTemplateKeys = Array.isArray(assignment.inputs.allowedTemplateKeys)
|
|
501
|
-
? Array.from(new Set(assignment.inputs.allowedTemplateKeys.map((value) => (typeof value === 'string' ? value.trim() : '')).filter(Boolean)))
|
|
535
|
+
const rawReviewAppVersionId = Number(reviewTarget?.appVersionId);
|
|
536
|
+
const reviewAppVersionId = Number.isInteger(rawReviewAppVersionId) && rawReviewAppVersionId > 0
|
|
537
|
+
? rawReviewAppVersionId
|
|
538
|
+
: null;
|
|
539
|
+
const allowedTemplateKeys = Array.isArray(metadata.allowedTemplateKeys)
|
|
540
|
+
? metadata.allowedTemplateKeys.filter((value) => typeof value === 'string' && Boolean(value.trim()))
|
|
502
541
|
: [];
|
|
503
|
-
const creatorRequest = assignment.inputs.request.prompt.trim();
|
|
504
|
-
if (!creatorRequest) {
|
|
505
|
-
throw new Error('agent_task_context_creator_request_missing');
|
|
506
|
-
}
|
|
507
542
|
return {
|
|
508
|
-
taskId:
|
|
509
|
-
attempt,
|
|
510
|
-
taskToken,
|
|
511
|
-
|
|
543
|
+
taskId: input.taskId,
|
|
544
|
+
attempt: input.attempt,
|
|
545
|
+
taskToken: input.taskToken,
|
|
546
|
+
taskType: input.taskType,
|
|
547
|
+
metadata: input.metadata,
|
|
548
|
+
kind,
|
|
512
549
|
creatorUsername,
|
|
513
|
-
creatorRequest,
|
|
514
|
-
target:
|
|
550
|
+
creatorRequest: input.prompt,
|
|
551
|
+
target: input.target,
|
|
515
552
|
outputAppName,
|
|
516
553
|
outputVersion,
|
|
517
554
|
remixSourceRef,
|
|
@@ -519,12 +556,62 @@ function buildWorkerTaskContext(claim, assignment) {
|
|
|
519
556
|
reviewAppVersionId,
|
|
520
557
|
};
|
|
521
558
|
}
|
|
559
|
+
function buildWorkerTaskContextV2(assignment) {
|
|
560
|
+
return buildWorkerTaskContext({
|
|
561
|
+
taskId: assignment.task.id,
|
|
562
|
+
attempt: assignment.task.attempt,
|
|
563
|
+
taskToken: assignment.task.token,
|
|
564
|
+
taskType: assignment.task.type,
|
|
565
|
+
metadata: assignment.task.metadata,
|
|
566
|
+
prompt: assignment.request.prompt,
|
|
567
|
+
target: assignment.task.executionTarget,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
522
570
|
function workerTaskContextPath(workspaceDir) {
|
|
523
571
|
return node_path_1.default.join(workspaceDir, '.playdrop', 'task.json');
|
|
524
572
|
}
|
|
525
573
|
function workerTaskUploadResultPath(workspaceDir) {
|
|
526
574
|
return node_path_1.default.join(workspaceDir, '.playdrop', 'task-upload-result.json');
|
|
527
575
|
}
|
|
576
|
+
function serializeWorkerTaskContext(taskContext) {
|
|
577
|
+
const playdrop = taskContext.metadata.playdrop;
|
|
578
|
+
if (!playdrop || typeof playdrop !== 'object' || Array.isArray(playdrop)) {
|
|
579
|
+
throw new Error('agent_task_context_playdrop_metadata_missing');
|
|
580
|
+
}
|
|
581
|
+
const playdropMetadata = playdrop;
|
|
582
|
+
const output = playdropMetadata.output;
|
|
583
|
+
if (!output || typeof output !== 'object' || Array.isArray(output)) {
|
|
584
|
+
throw new Error('agent_task_context_invalid');
|
|
585
|
+
}
|
|
586
|
+
const metadata = {
|
|
587
|
+
...taskContext.metadata,
|
|
588
|
+
playdrop: {
|
|
589
|
+
...playdropMetadata,
|
|
590
|
+
output: {
|
|
591
|
+
...output,
|
|
592
|
+
appName: taskContext.outputAppName,
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
return {
|
|
597
|
+
protocolVersion: 2,
|
|
598
|
+
task: {
|
|
599
|
+
id: taskContext.taskId,
|
|
600
|
+
attempt: taskContext.attempt,
|
|
601
|
+
token: taskContext.taskToken,
|
|
602
|
+
type: taskContext.taskType,
|
|
603
|
+
executionTarget: taskContext.target,
|
|
604
|
+
metadata,
|
|
605
|
+
},
|
|
606
|
+
request: {
|
|
607
|
+
prompt: taskContext.creatorRequest,
|
|
608
|
+
},
|
|
609
|
+
worker: {
|
|
610
|
+
devPort: taskContext.devPort,
|
|
611
|
+
env: taskContext.env,
|
|
612
|
+
},
|
|
613
|
+
};
|
|
614
|
+
}
|
|
528
615
|
async function stageWorkerTaskContext(input) {
|
|
529
616
|
const playdropDir = node_path_1.default.join(input.workspaceDir, '.playdrop');
|
|
530
617
|
await (0, promises_1.mkdir)(playdropDir, { recursive: true });
|
|
@@ -533,18 +620,10 @@ async function stageWorkerTaskContext(input) {
|
|
|
533
620
|
devPort: input.devPort,
|
|
534
621
|
env: input.env,
|
|
535
622
|
};
|
|
536
|
-
await (0, promises_1.writeFile)(workerTaskContextPath(input.workspaceDir), JSON.stringify(taskFile, null, 2), 'utf8');
|
|
537
|
-
await (0, promises_1.writeFile)(node_path_1.default.join(input.workspaceDir, '.playdrop.json'), JSON.stringify({
|
|
538
|
-
ownerUsername: input.taskContext.creatorUsername,
|
|
539
|
-
env: input.env,
|
|
540
|
-
taskId: input.taskContext.taskId,
|
|
541
|
-
taskAttempt: input.taskContext.attempt,
|
|
542
|
-
devPort: input.devPort,
|
|
543
|
-
taskToken: input.taskContext.taskToken,
|
|
544
|
-
}, null, 2), 'utf8');
|
|
623
|
+
await (0, promises_1.writeFile)(workerTaskContextPath(input.workspaceDir), JSON.stringify(serializeWorkerTaskContext(taskFile), null, 2), 'utf8');
|
|
545
624
|
}
|
|
546
625
|
async function writeWorkerTaskContextFile(workspaceDir, taskContext) {
|
|
547
|
-
await (0, promises_1.writeFile)(workerTaskContextPath(workspaceDir), JSON.stringify(taskContext, null, 2), 'utf8');
|
|
626
|
+
await (0, promises_1.writeFile)(workerTaskContextPath(workspaceDir), JSON.stringify(serializeWorkerTaskContext(taskContext), null, 2), 'utf8');
|
|
548
627
|
}
|
|
549
628
|
function findWorkspaceTaskFile(startDir, basename) {
|
|
550
629
|
let current = node_path_1.default.resolve(startDir);
|
|
@@ -563,67 +642,57 @@ function findWorkspaceTaskFile(startDir, basename) {
|
|
|
563
642
|
function readTaskContextFile(startDir = node_process_1.default.cwd()) {
|
|
564
643
|
const file = findWorkspaceTaskFile(startDir, 'task.json');
|
|
565
644
|
const parsed = JSON.parse((0, node_fs_1.readFileSync)(file, 'utf8'));
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
const taskToken = typeof parsed.taskToken === 'string' ? parsed.taskToken.trim() : '';
|
|
569
|
-
const creatorUsername = typeof parsed.creatorUsername === 'string' ? parsed.creatorUsername.trim() : '';
|
|
570
|
-
const env = typeof parsed.env === 'string' ? parsed.env.trim() : '';
|
|
571
|
-
const creatorRequest = typeof parsed.creatorRequest === 'string' ? parsed.creatorRequest.trim() : '';
|
|
572
|
-
const outputVersion = typeof parsed.outputVersion === 'string' ? parsed.outputVersion.trim() : '';
|
|
573
|
-
const remixSourceRef = typeof parsed.remixSourceRef === 'string' && parsed.remixSourceRef.trim()
|
|
574
|
-
? parsed.remixSourceRef.trim()
|
|
645
|
+
const task = parsed.task && typeof parsed.task === 'object' && !Array.isArray(parsed.task)
|
|
646
|
+
? parsed.task
|
|
575
647
|
: null;
|
|
576
|
-
const
|
|
577
|
-
?
|
|
578
|
-
:
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
const
|
|
587
|
-
|
|
648
|
+
const request = parsed.request && typeof parsed.request === 'object' && !Array.isArray(parsed.request)
|
|
649
|
+
? parsed.request
|
|
650
|
+
: null;
|
|
651
|
+
const worker = parsed.worker && typeof parsed.worker === 'object' && !Array.isArray(parsed.worker)
|
|
652
|
+
? parsed.worker
|
|
653
|
+
: null;
|
|
654
|
+
const taskId = Number(task?.id);
|
|
655
|
+
const attempt = Number(task?.attempt);
|
|
656
|
+
const taskToken = typeof task?.token === 'string' ? task.token.trim() : '';
|
|
657
|
+
const taskType = typeof task?.type === 'string' ? task.type.trim() : '';
|
|
658
|
+
const metadata = task?.metadata && typeof task.metadata === 'object' && !Array.isArray(task.metadata)
|
|
659
|
+
? task.metadata
|
|
660
|
+
: null;
|
|
661
|
+
const prompt = typeof request?.prompt === 'string' ? request.prompt : '';
|
|
662
|
+
const env = typeof worker?.env === 'string' ? worker.env.trim() : '';
|
|
663
|
+
const devPort = typeof worker?.devPort === 'number'
|
|
664
|
+
? worker.devPort
|
|
665
|
+
: Number.parseInt(String(worker?.devPort ?? ''), 10);
|
|
666
|
+
if (parsed.protocolVersion !== 2
|
|
667
|
+
|| !Number.isInteger(taskId) || taskId <= 0
|
|
588
668
|
|| !Number.isInteger(attempt) || attempt <= 0
|
|
589
669
|
|| !taskToken
|
|
590
|
-
||
|
|
591
|
-
||
|
|
592
|
-
||
|
|
593
|
-
|| !
|
|
670
|
+
|| !taskType
|
|
671
|
+
|| !metadata
|
|
672
|
+
|| (task?.executionTarget !== 'FIRST_PARTY' && task?.executionTarget !== 'PERSONAL')
|
|
673
|
+
|| !prompt.trim()
|
|
594
674
|
|| !env
|
|
595
|
-
|| !outputVersion
|
|
596
|
-
|| (parsed.kind === 'REMIX_GAME' && !remixSourceRef)
|
|
597
|
-
|| (parsed.kind === 'GAME_REVIEW' && reviewAppVersionId === null)
|
|
598
675
|
|| !Number.isInteger(devPort) || devPort <= 0 || devPort > 65535) {
|
|
599
676
|
throw new Error(`task_context_invalid:${file}`);
|
|
600
677
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
618
|
-
function normalizeAssignmentRequest(value) {
|
|
619
|
-
const request = value && typeof value === 'object' && !Array.isArray(value)
|
|
620
|
-
? value
|
|
621
|
-
: null;
|
|
622
|
-
const prompt = typeof request?.prompt === 'string' ? request.prompt.trim() : '';
|
|
623
|
-
if (!prompt) {
|
|
624
|
-
throw new Error('agent_task_assignment_request_prompt_missing');
|
|
678
|
+
try {
|
|
679
|
+
return {
|
|
680
|
+
...buildWorkerTaskContext({
|
|
681
|
+
taskId,
|
|
682
|
+
attempt,
|
|
683
|
+
taskToken,
|
|
684
|
+
taskType,
|
|
685
|
+
metadata,
|
|
686
|
+
prompt,
|
|
687
|
+
target: task.executionTarget,
|
|
688
|
+
}),
|
|
689
|
+
devPort,
|
|
690
|
+
env,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
catch {
|
|
694
|
+
throw new Error(`task_context_invalid:${file}`);
|
|
625
695
|
}
|
|
626
|
-
return prompt;
|
|
627
696
|
}
|
|
628
697
|
function hasAgentTaskUploadedArtifact(task) {
|
|
629
698
|
return ((Number.isInteger(task.appId) && Number(task.appId) > 0)
|
|
@@ -816,41 +885,6 @@ function normalizeAssignmentWorkspaceRelativePath(value) {
|
|
|
816
885
|
}
|
|
817
886
|
return normalizedPath;
|
|
818
887
|
}
|
|
819
|
-
function normalizeAssignmentWorkspaceFile(value) {
|
|
820
|
-
const file = value && typeof value === 'object' && !Array.isArray(value)
|
|
821
|
-
? value
|
|
822
|
-
: null;
|
|
823
|
-
if (!file) {
|
|
824
|
-
throw new Error('agent_task_assignment_workspace_file_invalid');
|
|
825
|
-
}
|
|
826
|
-
const relativePath = normalizeAssignmentWorkspaceRelativePath(file.relativePath);
|
|
827
|
-
const contentBase64 = typeof file.contentBase64 === 'string' ? file.contentBase64.trim() : '';
|
|
828
|
-
if (!contentBase64 || contentBase64.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(contentBase64)) {
|
|
829
|
-
throw new Error('agent_task_assignment_workspace_file_content_invalid');
|
|
830
|
-
}
|
|
831
|
-
const contentType = typeof file.contentType === 'string' && file.contentType.trim()
|
|
832
|
-
? file.contentType.trim()
|
|
833
|
-
: undefined;
|
|
834
|
-
return {
|
|
835
|
-
relativePath,
|
|
836
|
-
contentBase64,
|
|
837
|
-
...(contentType ? { contentType } : {}),
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
function normalizeAssignmentWorkspaceFiles(value) {
|
|
841
|
-
if (!Array.isArray(value)) {
|
|
842
|
-
return [];
|
|
843
|
-
}
|
|
844
|
-
const seen = new Set();
|
|
845
|
-
return value.map((entry) => {
|
|
846
|
-
const normalized = normalizeAssignmentWorkspaceFile(entry);
|
|
847
|
-
if (seen.has(normalized.relativePath)) {
|
|
848
|
-
throw new Error('agent_task_assignment_workspace_file_duplicate');
|
|
849
|
-
}
|
|
850
|
-
seen.add(normalized.relativePath);
|
|
851
|
-
return normalized;
|
|
852
|
-
});
|
|
853
|
-
}
|
|
854
888
|
function resolveWorkspaceFileDestination(workspaceDir, relativePath) {
|
|
855
889
|
const root = node_path_1.default.resolve(workspaceDir);
|
|
856
890
|
const destination = node_path_1.default.resolve(root, ...relativePath.split('/'));
|
|
@@ -860,12 +894,31 @@ function resolveWorkspaceFileDestination(workspaceDir, relativePath) {
|
|
|
860
894
|
}
|
|
861
895
|
return destination;
|
|
862
896
|
}
|
|
863
|
-
async function
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
|
|
897
|
+
async function stageAssignmentWorkspaceV2(workspaceDir, workspace) {
|
|
898
|
+
for (const file of workspace.files) {
|
|
899
|
+
const normalized = normalizeAssignmentContent({
|
|
900
|
+
path: file.path,
|
|
901
|
+
contentBase64: file.contentBase64,
|
|
902
|
+
sha256: file.sha256,
|
|
903
|
+
sizeBytes: file.sizeBytes,
|
|
904
|
+
code: 'agent_task_assignment_workspace_file',
|
|
905
|
+
});
|
|
906
|
+
const destination = resolveWorkspaceFileDestination(workspaceDir, normalized.path);
|
|
867
907
|
await (0, promises_1.mkdir)(node_path_1.default.dirname(destination), { recursive: true });
|
|
868
|
-
await (0, promises_1.writeFile)(destination,
|
|
908
|
+
await (0, promises_1.writeFile)(destination, normalized.buffer);
|
|
909
|
+
}
|
|
910
|
+
for (const archive of workspace.archives) {
|
|
911
|
+
const normalized = normalizeAssignmentContent({
|
|
912
|
+
path: archive.path,
|
|
913
|
+
contentBase64: archive.contentBase64,
|
|
914
|
+
sha256: archive.sha256,
|
|
915
|
+
sizeBytes: archive.sizeBytes,
|
|
916
|
+
code: 'agent_task_assignment_workspace_archive',
|
|
917
|
+
});
|
|
918
|
+
const destination = resolveWorkspaceFileDestination(workspaceDir, normalized.path);
|
|
919
|
+
await (0, promises_1.mkdir)(destination, { recursive: true });
|
|
920
|
+
extractZipArchive(new Uint8Array(normalized.buffer), destination);
|
|
921
|
+
await removeStaleWorkerControlFiles(destination);
|
|
869
922
|
}
|
|
870
923
|
}
|
|
871
924
|
function isFile(candidatePath) {
|
|
@@ -876,146 +929,6 @@ function isFile(candidatePath) {
|
|
|
876
929
|
return false;
|
|
877
930
|
}
|
|
878
931
|
}
|
|
879
|
-
function normalizePlaydropPluginManifestPath(input) {
|
|
880
|
-
if (typeof input.value !== 'string' || input.value.trim().length <= 0) {
|
|
881
|
-
throw new Error(`playdrop_plugin_staging_manifest_incomplete:${input.kind}: file paths must be non-empty strings in ${input.manifestPath}`);
|
|
882
|
-
}
|
|
883
|
-
if (input.value.includes('\0') || input.value.includes('\\')) {
|
|
884
|
-
throw new Error(`playdrop_plugin_staging_manifest_invalid_path:${input.kind}:${input.value}`);
|
|
885
|
-
}
|
|
886
|
-
if (node_path_1.default.isAbsolute(input.value) || /^[A-Za-z]:[\\/]/.test(input.value)) {
|
|
887
|
-
throw new Error(`playdrop_plugin_staging_manifest_invalid_path:${input.kind}:${input.value}`);
|
|
888
|
-
}
|
|
889
|
-
const normalized = node_path_1.default.posix.normalize(input.value);
|
|
890
|
-
if (normalized === '.' || normalized === '..' || normalized.startsWith('../')) {
|
|
891
|
-
throw new Error(`playdrop_plugin_staging_manifest_invalid_path:${input.kind}:${input.value}`);
|
|
892
|
-
}
|
|
893
|
-
return normalized;
|
|
894
|
-
}
|
|
895
|
-
function readPlaydropPluginStagingManifest(pluginRoot) {
|
|
896
|
-
const resolvedRoot = node_path_1.default.resolve(pluginRoot);
|
|
897
|
-
const manifestPath = node_path_1.default.join(resolvedRoot, PLAYDROP_PLUGIN_STAGING_MANIFEST_PATH);
|
|
898
|
-
if (!isFile(manifestPath)) {
|
|
899
|
-
throw new Error(`playdrop_plugin_staging_manifest_missing: expected ${PLAYDROP_PLUGIN_STAGING_MANIFEST_PATH} under ${resolvedRoot}`);
|
|
900
|
-
}
|
|
901
|
-
let parsed;
|
|
902
|
-
try {
|
|
903
|
-
parsed = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, 'utf8'));
|
|
904
|
-
}
|
|
905
|
-
catch (error) {
|
|
906
|
-
throw new Error(`playdrop_plugin_staging_manifest_invalid_json:${manifestPath}:${error instanceof Error ? error.message : String(error)}`);
|
|
907
|
-
}
|
|
908
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
909
|
-
throw new Error(`playdrop_plugin_staging_manifest_invalid:${manifestPath}`);
|
|
910
|
-
}
|
|
911
|
-
const rawManifest = parsed;
|
|
912
|
-
if (rawManifest.version !== 1) {
|
|
913
|
-
throw new Error(`playdrop_plugin_staging_manifest_unsupported_version:${manifestPath}`);
|
|
914
|
-
}
|
|
915
|
-
if (!rawManifest.taskFiles || typeof rawManifest.taskFiles !== 'object' || Array.isArray(rawManifest.taskFiles)) {
|
|
916
|
-
throw new Error(`playdrop_plugin_staging_manifest_incomplete: taskFiles missing in ${manifestPath}`);
|
|
917
|
-
}
|
|
918
|
-
const taskFiles = {};
|
|
919
|
-
const rawTaskFiles = rawManifest.taskFiles;
|
|
920
|
-
for (const kind of PLAYDROP_PLUGIN_MANIFEST_REQUIRED_STARTUP_KINDS) {
|
|
921
|
-
const rawFiles = rawTaskFiles[kind];
|
|
922
|
-
if (!Array.isArray(rawFiles) || rawFiles.length <= 0) {
|
|
923
|
-
throw new Error(`playdrop_plugin_staging_manifest_incomplete:${kind}: non-empty taskFiles entry required in ${manifestPath}`);
|
|
924
|
-
}
|
|
925
|
-
const seen = new Set();
|
|
926
|
-
const files = rawFiles.map((value) => normalizePlaydropPluginManifestPath({ manifestPath, kind, value }));
|
|
927
|
-
for (const relativePath of files) {
|
|
928
|
-
if (seen.has(relativePath)) {
|
|
929
|
-
throw new Error(`playdrop_plugin_staging_manifest_duplicate_file:${kind}:${relativePath}`);
|
|
930
|
-
}
|
|
931
|
-
seen.add(relativePath);
|
|
932
|
-
const source = node_path_1.default.resolve(resolvedRoot, ...relativePath.split('/'));
|
|
933
|
-
const relativeToRoot = node_path_1.default.relative(resolvedRoot, source);
|
|
934
|
-
if (relativeToRoot.startsWith('..') || node_path_1.default.isAbsolute(relativeToRoot)) {
|
|
935
|
-
throw new Error(`playdrop_plugin_staging_manifest_invalid_path:${kind}:${relativePath}`);
|
|
936
|
-
}
|
|
937
|
-
if (!isFile(source)) {
|
|
938
|
-
throw new Error(`playdrop_plugin_staging_manifest_file_missing:${kind}:${relativePath}`);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
taskFiles[kind] = files;
|
|
942
|
-
}
|
|
943
|
-
for (const kind of WORKER_SUPPORTED_KINDS) {
|
|
944
|
-
if (taskFiles[kind]) {
|
|
945
|
-
continue;
|
|
946
|
-
}
|
|
947
|
-
const rawFiles = rawTaskFiles[kind];
|
|
948
|
-
if (rawFiles === undefined) {
|
|
949
|
-
continue;
|
|
950
|
-
}
|
|
951
|
-
if (!Array.isArray(rawFiles) || rawFiles.length <= 0) {
|
|
952
|
-
throw new Error(`playdrop_plugin_staging_manifest_incomplete:${kind}: non-empty taskFiles entry required in ${manifestPath}`);
|
|
953
|
-
}
|
|
954
|
-
const seen = new Set();
|
|
955
|
-
const files = rawFiles.map((value) => normalizePlaydropPluginManifestPath({ manifestPath, kind, value }));
|
|
956
|
-
for (const relativePath of files) {
|
|
957
|
-
if (seen.has(relativePath)) {
|
|
958
|
-
throw new Error(`playdrop_plugin_staging_manifest_duplicate_file:${kind}:${relativePath}`);
|
|
959
|
-
}
|
|
960
|
-
seen.add(relativePath);
|
|
961
|
-
const source = node_path_1.default.resolve(resolvedRoot, ...relativePath.split('/'));
|
|
962
|
-
const relativeToRoot = node_path_1.default.relative(resolvedRoot, source);
|
|
963
|
-
if (relativeToRoot.startsWith('..') || node_path_1.default.isAbsolute(relativeToRoot)) {
|
|
964
|
-
throw new Error(`playdrop_plugin_staging_manifest_invalid_path:${kind}:${relativePath}`);
|
|
965
|
-
}
|
|
966
|
-
if (!isFile(source)) {
|
|
967
|
-
throw new Error(`playdrop_plugin_staging_manifest_file_missing:${kind}:${relativePath}`);
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
taskFiles[kind] = files;
|
|
971
|
-
}
|
|
972
|
-
return {
|
|
973
|
-
version: 1,
|
|
974
|
-
taskFiles: taskFiles,
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
async function stagePlaydropPluginReferences(input) {
|
|
978
|
-
const pluginRoot = node_path_1.default.resolve(input.pluginRoot);
|
|
979
|
-
if (!WORKER_SUPPORTED_KINDS.includes(input.kind)) {
|
|
980
|
-
throw new Error(`unsupported_agent_task_kind:${input.kind}`);
|
|
981
|
-
}
|
|
982
|
-
const manifest = readPlaydropPluginStagingManifest(pluginRoot);
|
|
983
|
-
const referenceFiles = manifest.taskFiles[input.kind];
|
|
984
|
-
if (!Array.isArray(referenceFiles) || referenceFiles.length <= 0) {
|
|
985
|
-
throw new Error(`playdrop_plugin_staging_manifest_incomplete:${input.kind}: non-empty taskFiles entry required`);
|
|
986
|
-
}
|
|
987
|
-
const staged = [];
|
|
988
|
-
for (const relativePath of referenceFiles) {
|
|
989
|
-
const destinationRelativePath = node_path_1.default.join(STAGED_PLAYDROP_PLUGIN_ROOT, relativePath);
|
|
990
|
-
const destination = resolveWorkspaceFileDestination(input.workspaceDir, destinationRelativePath);
|
|
991
|
-
await (0, promises_1.mkdir)(node_path_1.default.dirname(destination), { recursive: true });
|
|
992
|
-
await (0, promises_1.copyFile)(node_path_1.default.join(pluginRoot, relativePath), destination);
|
|
993
|
-
staged.push(destinationRelativePath.replace(/\\/g, '/'));
|
|
994
|
-
}
|
|
995
|
-
return staged;
|
|
996
|
-
}
|
|
997
|
-
async function stageCodexTaskSkills(input) {
|
|
998
|
-
const skillRoot = node_path_1.default.join(input.workspaceDir, STAGED_CODEX_SKILL_ROOT);
|
|
999
|
-
await (0, promises_1.rm)(skillRoot, { recursive: true, force: true });
|
|
1000
|
-
const stagedSkills = [];
|
|
1001
|
-
for (const stagedPluginPath of input.stagedPluginPaths) {
|
|
1002
|
-
const normalized = stagedPluginPath.replace(/\\/g, '/');
|
|
1003
|
-
const match = /^\.playdrop\/plugin\/skills\/([^/]+)\/SKILL\.md$/.exec(normalized);
|
|
1004
|
-
if (!match?.[1]) {
|
|
1005
|
-
continue;
|
|
1006
|
-
}
|
|
1007
|
-
const source = resolveWorkspaceFileDestination(input.workspaceDir, normalized);
|
|
1008
|
-
const destinationRelativePath = `${STAGED_CODEX_SKILL_ROOT}/${match[1]}/SKILL.md`;
|
|
1009
|
-
const destination = resolveWorkspaceFileDestination(input.workspaceDir, destinationRelativePath);
|
|
1010
|
-
await (0, promises_1.mkdir)(node_path_1.default.dirname(destination), { recursive: true });
|
|
1011
|
-
await (0, promises_1.copyFile)(source, destination);
|
|
1012
|
-
stagedSkills.push(destinationRelativePath);
|
|
1013
|
-
}
|
|
1014
|
-
if (stagedSkills.length <= 0) {
|
|
1015
|
-
throw new Error('agent_task_codex_skills_missing');
|
|
1016
|
-
}
|
|
1017
|
-
return stagedSkills.sort();
|
|
1018
|
-
}
|
|
1019
932
|
function assertAssignmentPluginBundleExtractionBounds(buffer) {
|
|
1020
933
|
if (buffer.length < ZIP_END_OF_CENTRAL_DIRECTORY_MIN_BYTES) {
|
|
1021
934
|
throw new Error('agent_task_plugin_bundle_invalid_zip');
|
|
@@ -1081,15 +994,102 @@ function assertAssignmentPluginBundleExtractionBounds(buffer) {
|
|
|
1081
994
|
throw new Error('agent_task_plugin_bundle_invalid_zip');
|
|
1082
995
|
}
|
|
1083
996
|
}
|
|
997
|
+
function normalizeAgentBundlePath(root, value, code) {
|
|
998
|
+
const relativePath = typeof value === 'string' ? value.trim().replace(/\\/g, '/') : '';
|
|
999
|
+
if (!relativePath
|
|
1000
|
+
|| relativePath.includes('\0')
|
|
1001
|
+
|| node_path_1.default.posix.isAbsolute(relativePath)
|
|
1002
|
+
|| relativePath === '..'
|
|
1003
|
+
|| relativePath.startsWith('../')) {
|
|
1004
|
+
throw new Error(code);
|
|
1005
|
+
}
|
|
1006
|
+
const resolved = node_path_1.default.resolve(root, ...relativePath.split('/'));
|
|
1007
|
+
if (!isPathInside(root, resolved)) {
|
|
1008
|
+
throw new Error(code);
|
|
1009
|
+
}
|
|
1010
|
+
return resolved;
|
|
1011
|
+
}
|
|
1012
|
+
function readAgentBundleManifest(extractedRoot) {
|
|
1013
|
+
const manifestPath = node_path_1.default.join(extractedRoot, AGENT_BUNDLE_MANIFEST_PATH);
|
|
1014
|
+
if (!isFile(manifestPath)) {
|
|
1015
|
+
throw new Error('agent_bundle_manifest_missing');
|
|
1016
|
+
}
|
|
1017
|
+
let parsed;
|
|
1018
|
+
try {
|
|
1019
|
+
parsed = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, 'utf8'));
|
|
1020
|
+
}
|
|
1021
|
+
catch {
|
|
1022
|
+
throw new Error('agent_bundle_manifest_invalid_json');
|
|
1023
|
+
}
|
|
1024
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
1025
|
+
throw new Error('agent_bundle_manifest_invalid');
|
|
1026
|
+
}
|
|
1027
|
+
const manifest = parsed;
|
|
1028
|
+
if (manifest.version !== 1
|
|
1029
|
+
|| !manifest.plugin
|
|
1030
|
+
|| typeof manifest.plugin.root !== 'string'
|
|
1031
|
+
|| !manifest.workspace
|
|
1032
|
+
|| !Array.isArray(manifest.workspace.files)) {
|
|
1033
|
+
throw new Error('agent_bundle_manifest_invalid');
|
|
1034
|
+
}
|
|
1035
|
+
const pluginRoot = normalizeAgentBundlePath(extractedRoot, manifest.plugin.root, 'agent_bundle_plugin_root_invalid');
|
|
1036
|
+
if (!isFile(node_path_1.default.join(pluginRoot, '.codex-plugin', 'plugin.json'))) {
|
|
1037
|
+
throw new Error('agent_bundle_codex_plugin_manifest_missing');
|
|
1038
|
+
}
|
|
1039
|
+
if (!isFile(node_path_1.default.join(pluginRoot, '.claude-plugin', 'plugin.json'))) {
|
|
1040
|
+
throw new Error('agent_bundle_claude_plugin_manifest_missing');
|
|
1041
|
+
}
|
|
1042
|
+
const destinations = new Set();
|
|
1043
|
+
for (const entry of manifest.workspace.files) {
|
|
1044
|
+
const source = normalizeAgentBundlePath(extractedRoot, entry?.source, 'agent_bundle_workspace_source_invalid');
|
|
1045
|
+
if (!isFile(source)) {
|
|
1046
|
+
throw new Error(`agent_bundle_workspace_source_missing:${entry?.source ?? ''}`);
|
|
1047
|
+
}
|
|
1048
|
+
const destination = typeof entry?.destination === 'string'
|
|
1049
|
+
? entry.destination.trim().replace(/\\/g, '/')
|
|
1050
|
+
: '';
|
|
1051
|
+
if (!destination
|
|
1052
|
+
|| node_path_1.default.posix.isAbsolute(destination)
|
|
1053
|
+
|| destination === '..'
|
|
1054
|
+
|| destination.startsWith('../')) {
|
|
1055
|
+
throw new Error('agent_bundle_workspace_destination_invalid');
|
|
1056
|
+
}
|
|
1057
|
+
destinations.add(destination);
|
|
1058
|
+
}
|
|
1059
|
+
if (!destinations.has('AGENTS.md')) {
|
|
1060
|
+
throw new Error('agent_bundle_agents_file_missing');
|
|
1061
|
+
}
|
|
1062
|
+
if (!destinations.has('CLAUDE.md')) {
|
|
1063
|
+
throw new Error('agent_bundle_claude_file_missing');
|
|
1064
|
+
}
|
|
1065
|
+
return { manifest, pluginRoot };
|
|
1066
|
+
}
|
|
1067
|
+
async function stageAgentBundleWorkspaceFiles(extractedRoot, workspaceDir) {
|
|
1068
|
+
const resolved = readAgentBundleManifest(extractedRoot);
|
|
1069
|
+
for (const entry of resolved.manifest.workspace.files) {
|
|
1070
|
+
const source = normalizeAgentBundlePath(extractedRoot, entry.source, 'agent_bundle_workspace_source_invalid');
|
|
1071
|
+
const destination = resolveWorkspaceFileDestination(workspaceDir, entry.destination);
|
|
1072
|
+
await (0, promises_1.mkdir)(node_path_1.default.dirname(destination), { recursive: true });
|
|
1073
|
+
await (0, promises_1.copyFile)(source, destination);
|
|
1074
|
+
}
|
|
1075
|
+
return resolved.pluginRoot;
|
|
1076
|
+
}
|
|
1084
1077
|
async function stageAssignmentPluginBundle(input) {
|
|
1085
1078
|
const sha256 = typeof input.pluginBundle.sha256 === 'string' ? input.pluginBundle.sha256.trim() : '';
|
|
1079
|
+
const fileName = typeof input.pluginBundle.fileName === 'string' ? input.pluginBundle.fileName.trim() : '';
|
|
1086
1080
|
const contentBase64 = typeof input.pluginBundle.contentBase64 === 'string' ? input.pluginBundle.contentBase64.trim() : '';
|
|
1087
|
-
|
|
1081
|
+
const sizeBytes = Number(input.pluginBundle.sizeBytes);
|
|
1082
|
+
if (!/^[a-f0-9]{64}$/.test(sha256)
|
|
1083
|
+
|| !fileName
|
|
1084
|
+
|| node_path_1.default.basename(fileName) !== fileName
|
|
1085
|
+
|| !Number.isInteger(sizeBytes)
|
|
1086
|
+
|| sizeBytes <= 0
|
|
1087
|
+
|| !contentBase64) {
|
|
1088
1088
|
throw new Error('agent_task_plugin_bundle_invalid');
|
|
1089
1089
|
}
|
|
1090
1090
|
const buffer = Buffer.from(contentBase64, 'base64');
|
|
1091
|
-
if (buffer.length
|
|
1092
|
-
throw new Error('
|
|
1091
|
+
if (buffer.length !== sizeBytes) {
|
|
1092
|
+
throw new Error('agent_task_plugin_bundle_size_mismatch');
|
|
1093
1093
|
}
|
|
1094
1094
|
const actualSha = (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
|
|
1095
1095
|
if (actualSha !== sha256) {
|
|
@@ -1127,8 +1127,7 @@ async function stageAssignmentPluginBundle(input) {
|
|
|
1127
1127
|
await (0, promises_1.mkdir)(node_path_1.default.dirname(destination), { recursive: true });
|
|
1128
1128
|
await (0, promises_1.writeFile)(destination, Buffer.from(content));
|
|
1129
1129
|
}
|
|
1130
|
-
|
|
1131
|
-
return pluginRoot;
|
|
1130
|
+
return await stageAgentBundleWorkspaceFiles(pluginRoot, input.workspaceDir);
|
|
1132
1131
|
}
|
|
1133
1132
|
function resolveDevPluginWorkingTree(input) {
|
|
1134
1133
|
const workingTree = input.workingTree?.trim();
|
|
@@ -1144,7 +1143,7 @@ function resolveDevPluginWorkingTree(input) {
|
|
|
1144
1143
|
throw new Error(`worker_dev_plugin_working_tree_requires_dev_environment:${input.env}`);
|
|
1145
1144
|
}
|
|
1146
1145
|
const resolved = node_path_1.default.resolve(workingTree);
|
|
1147
|
-
|
|
1146
|
+
readAgentBundleManifest(resolved);
|
|
1148
1147
|
return resolved;
|
|
1149
1148
|
}
|
|
1150
1149
|
// A PlayDrop project root is the topmost directory holding a catalogue.json,
|
|
@@ -1502,6 +1501,7 @@ function buildWorkerCapabilities(input) {
|
|
|
1502
1501
|
cliVersion: codexVersion,
|
|
1503
1502
|
authenticated: codexAuthenticated,
|
|
1504
1503
|
models: codexModels,
|
|
1504
|
+
acceptsCustomModels: true,
|
|
1505
1505
|
ready: codexAuthenticated && codexModels.length > 0,
|
|
1506
1506
|
});
|
|
1507
1507
|
}
|
|
@@ -1517,6 +1517,7 @@ function buildWorkerCapabilities(input) {
|
|
|
1517
1517
|
cliVersion: claudeVersion,
|
|
1518
1518
|
authenticated: claudeAuthenticated,
|
|
1519
1519
|
models: claudeModels,
|
|
1520
|
+
acceptsCustomModels: true,
|
|
1520
1521
|
ready: claudeAuthenticated && claudeModels.length > 0,
|
|
1521
1522
|
});
|
|
1522
1523
|
}
|
|
@@ -1555,6 +1556,7 @@ function buildWorkerCapabilities(input) {
|
|
|
1555
1556
|
}
|
|
1556
1557
|
function buildWorkerClaimBody(input) {
|
|
1557
1558
|
return {
|
|
1559
|
+
protocolVersion: 2,
|
|
1558
1560
|
workerKey: input.workerKey,
|
|
1559
1561
|
capabilities: {
|
|
1560
1562
|
...input.capabilities,
|
|
@@ -1562,89 +1564,43 @@ function buildWorkerClaimBody(input) {
|
|
|
1562
1564
|
},
|
|
1563
1565
|
};
|
|
1564
1566
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
throw new Error('agent_task_assignment_missing_base_source');
|
|
1568
|
-
}
|
|
1569
|
-
if (typeof value.appName !== 'string' || !value.appName.trim()) {
|
|
1570
|
-
throw new Error('agent_task_base_app_name_missing');
|
|
1571
|
-
}
|
|
1572
|
-
if (typeof value.version !== 'string' || !value.version.trim()) {
|
|
1573
|
-
throw new Error('agent_task_base_app_version_missing');
|
|
1574
|
-
}
|
|
1575
|
-
if (typeof value.appId !== 'number' || !Number.isInteger(value.appId) || value.appId <= 0) {
|
|
1576
|
-
throw new Error('agent_task_base_app_id_missing');
|
|
1577
|
-
}
|
|
1578
|
-
if (typeof value.appVersionId !== 'number' || !Number.isInteger(value.appVersionId) || value.appVersionId <= 0) {
|
|
1579
|
-
throw new Error('agent_task_base_app_version_id_missing');
|
|
1580
|
-
}
|
|
1581
|
-
return {
|
|
1582
|
-
appName: value.appName.trim(),
|
|
1583
|
-
version: value.version.trim(),
|
|
1584
|
-
appId: value.appId,
|
|
1585
|
-
appVersionId: value.appVersionId,
|
|
1586
|
-
};
|
|
1587
|
-
}
|
|
1588
|
-
// Extracts a zip archive into targetDir using fflate (already a CLI
|
|
1589
|
-
// dependency; see src/commands/create.ts). Entries that resolve outside the
|
|
1590
|
-
// target directory throw instead of being skipped: a base source archive with
|
|
1591
|
-
// traversal entries is corrupt and must fail the task loudly.
|
|
1567
|
+
// Extracts a server-provided workspace ZIP using fflate. Entries that resolve
|
|
1568
|
+
// outside the target directory fail the task immediately.
|
|
1592
1569
|
function extractZipArchive(zipBuffer, targetDir) {
|
|
1593
1570
|
const files = (0, fflate_1.unzipSync)(zipBuffer);
|
|
1594
1571
|
const names = Object.keys(files);
|
|
1595
1572
|
if (names.length === 0) {
|
|
1596
|
-
throw new Error('
|
|
1573
|
+
throw new Error('agent_task_workspace_archive_empty');
|
|
1574
|
+
}
|
|
1575
|
+
if (names.length > ASSIGNMENT_PLUGIN_BUNDLE_MAX_FILES) {
|
|
1576
|
+
throw new Error(`agent_task_workspace_archive_too_many_entries:${names.length}>${ASSIGNMENT_PLUGIN_BUNDLE_MAX_FILES}`);
|
|
1597
1577
|
}
|
|
1598
1578
|
const resolvedTarget = node_path_1.default.resolve(targetDir);
|
|
1599
1579
|
const written = [];
|
|
1580
|
+
let uncompressedBytes = 0;
|
|
1600
1581
|
for (const originalName of names) {
|
|
1601
1582
|
const name = originalName.replace(/\\/g, '/');
|
|
1583
|
+
if (!name || name.startsWith('/') || name.split('/').includes('..')) {
|
|
1584
|
+
throw new Error(`agent_task_workspace_archive_unsafe_entry:${originalName}`);
|
|
1585
|
+
}
|
|
1602
1586
|
const destination = node_path_1.default.resolve(resolvedTarget, name);
|
|
1603
1587
|
if (destination !== resolvedTarget && !destination.startsWith(resolvedTarget + node_path_1.default.sep)) {
|
|
1604
|
-
throw new Error(`
|
|
1588
|
+
throw new Error(`agent_task_workspace_archive_unsafe_entry:${originalName}`);
|
|
1605
1589
|
}
|
|
1606
1590
|
if (name.endsWith('/')) {
|
|
1607
1591
|
(0, node_fs_1.mkdirSync)(destination, { recursive: true });
|
|
1608
1592
|
continue;
|
|
1609
1593
|
}
|
|
1594
|
+
uncompressedBytes += files[originalName].byteLength;
|
|
1595
|
+
if (uncompressedBytes > ASSIGNMENT_WORKSPACE_ARCHIVE_MAX_UNCOMPRESSED_BYTES) {
|
|
1596
|
+
throw new Error(`agent_task_workspace_archive_too_large:${uncompressedBytes}>${ASSIGNMENT_WORKSPACE_ARCHIVE_MAX_UNCOMPRESSED_BYTES}`);
|
|
1597
|
+
}
|
|
1610
1598
|
(0, node_fs_1.mkdirSync)(node_path_1.default.dirname(destination), { recursive: true });
|
|
1611
1599
|
(0, node_fs_1.writeFileSync)(destination, Buffer.from(files[originalName]));
|
|
1612
1600
|
written.push(name);
|
|
1613
1601
|
}
|
|
1614
1602
|
return written;
|
|
1615
1603
|
}
|
|
1616
|
-
// Reads the version string of the named app from the catalogue.json at the
|
|
1617
|
-
// project root. Used for the staged base version and the version-bump guard.
|
|
1618
|
-
function readWorkerProjectVersion(projectDir, appName) {
|
|
1619
|
-
const cataloguePath = node_path_1.default.join(projectDir, 'catalogue.json');
|
|
1620
|
-
let raw;
|
|
1621
|
-
try {
|
|
1622
|
-
raw = (0, node_fs_1.readFileSync)(cataloguePath, 'utf8');
|
|
1623
|
-
}
|
|
1624
|
-
catch (error) {
|
|
1625
|
-
throw new Error(`agent_task_catalogue_unreadable: could not read ${cataloguePath} (${error instanceof Error ? error.message : String(error)}).`);
|
|
1626
|
-
}
|
|
1627
|
-
let parsed;
|
|
1628
|
-
try {
|
|
1629
|
-
parsed = JSON.parse(raw);
|
|
1630
|
-
}
|
|
1631
|
-
catch {
|
|
1632
|
-
throw new Error(`agent_task_catalogue_invalid_json: ${cataloguePath} is not valid JSON.`);
|
|
1633
|
-
}
|
|
1634
|
-
const apps = parsed?.apps;
|
|
1635
|
-
if (!Array.isArray(apps)) {
|
|
1636
|
-
throw new Error(`agent_task_catalogue_apps_missing: ${cataloguePath} has no "apps" array.`);
|
|
1637
|
-
}
|
|
1638
|
-
const entry = apps.find((candidate) => Boolean(candidate) && typeof candidate === 'object' && candidate.name === appName);
|
|
1639
|
-
if (!entry) {
|
|
1640
|
-
throw new Error(`agent_task_catalogue_app_missing: no app named "${appName}" in ${cataloguePath}.`);
|
|
1641
|
-
}
|
|
1642
|
-
const version = typeof entry.version === 'string' ? entry.version.trim() : '';
|
|
1643
|
-
if (!version) {
|
|
1644
|
-
throw new Error(`agent_task_catalogue_version_missing: app "${appName}" in ${cataloguePath} has no version string.`);
|
|
1645
|
-
}
|
|
1646
|
-
return version;
|
|
1647
|
-
}
|
|
1648
1604
|
function normalizePreviewString(value) {
|
|
1649
1605
|
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
1650
1606
|
return normalized || undefined;
|
|
@@ -1827,52 +1783,40 @@ async function drainWorkerEventQueue(input) {
|
|
|
1827
1783
|
(0, node_fs_1.rmSync)(filePath, { force: true });
|
|
1828
1784
|
}
|
|
1829
1785
|
}
|
|
1830
|
-
|
|
1831
|
-
// version anyway, but failing here gives the task a precise error code.
|
|
1832
|
-
function assertWorkerProjectVersionBumped(input) {
|
|
1833
|
-
if (input.projectVersion === input.baseVersion) {
|
|
1834
|
-
throw new Error(`agent_task_version_not_bumped: app "${input.appName}" version is still "${input.baseVersion}"; the update must bump the catalogue.json version before publish.`);
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
async function removeStaleWorkerControlFilesFromBaseSource(stagingDir) {
|
|
1786
|
+
async function removeStaleWorkerControlFiles(stagingDir) {
|
|
1838
1787
|
await (0, promises_1.rm)(node_path_1.default.join(stagingDir, '.playdrop.json'), { force: true });
|
|
1839
1788
|
}
|
|
1840
|
-
|
|
1841
|
-
// the project root (catalogue.json) lands inside the workspace before Codex
|
|
1842
|
-
// starts. Returns the staged project root and its catalogue version string.
|
|
1843
|
-
async function stageWorkerBaseAppSource(input) {
|
|
1844
|
-
const { blob } = await input.client.downloadAppSource(input.creatorUsername, input.baseApp.appName, input.baseApp.version);
|
|
1845
|
-
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
1846
|
-
const stagingDir = node_path_1.default.join(input.workspaceDir, input.baseApp.appName);
|
|
1847
|
-
(0, node_fs_1.mkdirSync)(stagingDir, { recursive: true });
|
|
1848
|
-
extractZipArchive(new Uint8Array(buffer), stagingDir);
|
|
1849
|
-
await removeStaleWorkerControlFilesFromBaseSource(stagingDir);
|
|
1850
|
-
const stagedProjectDir = discoverWorkerProjectRoot(stagingDir);
|
|
1851
|
-
const stagedBaseVersion = readWorkerProjectVersion(stagedProjectDir, input.baseApp.appName);
|
|
1852
|
-
return { stagedProjectDir, stagedBaseVersion };
|
|
1853
|
-
}
|
|
1854
|
-
function resolveCodexModel(model) {
|
|
1789
|
+
function resolveCodexModel(model, reasoningEffortOverride) {
|
|
1855
1790
|
const normalized = model.trim();
|
|
1856
1791
|
if (!normalized) {
|
|
1857
1792
|
throw new Error('agent_task_assignment_model_missing');
|
|
1858
1793
|
}
|
|
1859
|
-
const effortMatch = normalized.match(/^(.*?)-(extra-high|low|medium|high|xhigh|max)$/);
|
|
1794
|
+
const effortMatch = normalized.match(/^(.*?)-(extra-high|low|medium|high|xhigh|max|ultra)$/);
|
|
1795
|
+
const legacyEffort = effortMatch?.[2] === 'extra-high' ? 'xhigh' : effortMatch?.[2];
|
|
1796
|
+
const reasoningEffort = reasoningEffortOverride?.trim() || legacyEffort || 'medium';
|
|
1797
|
+
if (reasoningEffort.length > 40 || /\s/.test(reasoningEffort)) {
|
|
1798
|
+
throw new Error('invalid_agent_task_reasoning_effort');
|
|
1799
|
+
}
|
|
1860
1800
|
if (effortMatch?.[1] && effortMatch[2]) {
|
|
1861
|
-
|
|
1862
|
-
return { model: effortMatch[1], reasoningEffort: effort };
|
|
1801
|
+
return { model: effortMatch[1], reasoningEffort };
|
|
1863
1802
|
}
|
|
1864
|
-
return { model: normalized, reasoningEffort
|
|
1803
|
+
return { model: normalized, reasoningEffort };
|
|
1865
1804
|
}
|
|
1866
|
-
function resolveClaudeModel(model) {
|
|
1805
|
+
function resolveClaudeModel(model, reasoningEffortOverride) {
|
|
1867
1806
|
const normalized = model.trim();
|
|
1868
1807
|
if (!normalized) {
|
|
1869
1808
|
throw new Error('agent_task_assignment_model_missing');
|
|
1870
1809
|
}
|
|
1871
|
-
const effortMatch = normalized.match(/^(.*)-(low|medium|high|xhigh|max)$/);
|
|
1810
|
+
const effortMatch = normalized.match(/^(.*)-(extra-high|low|medium|high|xhigh|max|ultra)$/);
|
|
1811
|
+
const legacyEffort = effortMatch?.[2] === 'extra-high' ? 'xhigh' : effortMatch?.[2] ?? null;
|
|
1812
|
+
const effort = reasoningEffortOverride?.trim() || legacyEffort;
|
|
1813
|
+
if (effort && (effort.length > 40 || /\s/.test(effort))) {
|
|
1814
|
+
throw new Error('invalid_agent_task_reasoning_effort');
|
|
1815
|
+
}
|
|
1872
1816
|
if (effortMatch?.[1] && effortMatch[2]) {
|
|
1873
|
-
return { model: resolveClaudeRuntimeModel(effortMatch[1]), effort
|
|
1817
|
+
return { model: resolveClaudeRuntimeModel(effortMatch[1]), effort };
|
|
1874
1818
|
}
|
|
1875
|
-
return { model: resolveClaudeRuntimeModel(normalized), effort
|
|
1819
|
+
return { model: resolveClaudeRuntimeModel(normalized), effort };
|
|
1876
1820
|
}
|
|
1877
1821
|
function resolveClaudeRuntimeModel(model) {
|
|
1878
1822
|
if (model === 'claude-haiku-4-5') {
|
|
@@ -1987,6 +1931,21 @@ async function runCodex(input) {
|
|
|
1987
1931
|
workerUsername: input.workerUsername,
|
|
1988
1932
|
envName: input.envName,
|
|
1989
1933
|
});
|
|
1934
|
+
if (input.taskPluginRoot) {
|
|
1935
|
+
const pluginEnv = {
|
|
1936
|
+
...node_process_1.default.env,
|
|
1937
|
+
HOME: homeDir,
|
|
1938
|
+
CODEX_HOME: codexHomeDir,
|
|
1939
|
+
};
|
|
1940
|
+
await execFileAsync('codex', ['plugin', 'marketplace', 'add', input.taskPluginRoot, '--json'], {
|
|
1941
|
+
cwd: input.workspaceDir,
|
|
1942
|
+
env: pluginEnv,
|
|
1943
|
+
});
|
|
1944
|
+
await execFileAsync('codex', ['plugin', 'add', 'playdrop@playdrop', '--json'], {
|
|
1945
|
+
cwd: input.workspaceDir,
|
|
1946
|
+
env: pluginEnv,
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1990
1949
|
let observedCodexThreadId = null;
|
|
1991
1950
|
let codexThreadProbe = '';
|
|
1992
1951
|
const result = await (0, runtime_1.runLoggedProcess)({
|
|
@@ -1997,11 +1956,13 @@ async function runCodex(input) {
|
|
|
1997
1956
|
reasoningEffort: input.codexModel.reasoningEffort,
|
|
1998
1957
|
networkAccess: (0, runtime_1.readEnvBoolean)('PLAYDROP_WORKER_AGENT_NETWORK_ACCESS', false),
|
|
1999
1958
|
sandboxMode: (0, runtime_1.readCodexSandboxMode)(),
|
|
1959
|
+
imagePaths: input.imagePaths,
|
|
2000
1960
|
}),
|
|
2001
1961
|
cwd: input.workspaceDir,
|
|
2002
1962
|
env: (0, runtime_1.buildWorkerChildEnv)({
|
|
2003
1963
|
binDir: input.binDir,
|
|
2004
1964
|
taskId: input.taskId,
|
|
1965
|
+
taskToken: input.taskToken,
|
|
2005
1966
|
attempt: input.attempt,
|
|
2006
1967
|
envName: input.envName,
|
|
2007
1968
|
eventDir: input.eventDir,
|
|
@@ -2082,6 +2043,7 @@ async function runClaude(input) {
|
|
|
2082
2043
|
env: (0, runtime_1.buildWorkerChildEnv)({
|
|
2083
2044
|
binDir: input.binDir,
|
|
2084
2045
|
taskId: input.taskId,
|
|
2046
|
+
taskToken: input.taskToken,
|
|
2085
2047
|
attempt: input.attempt,
|
|
2086
2048
|
envName: input.envName,
|
|
2087
2049
|
eventDir: input.eventDir,
|
|
@@ -2089,7 +2051,23 @@ async function runClaude(input) {
|
|
|
2089
2051
|
playdropConfigPath,
|
|
2090
2052
|
playwrightBrowsersPath: resolveWorkerPlaywrightBrowsersPath(),
|
|
2091
2053
|
}),
|
|
2092
|
-
stdin:
|
|
2054
|
+
stdin: `${JSON.stringify({
|
|
2055
|
+
type: 'user',
|
|
2056
|
+
message: {
|
|
2057
|
+
role: 'user',
|
|
2058
|
+
content: [
|
|
2059
|
+
{ type: 'text', text: input.prompt },
|
|
2060
|
+
...input.imageAttachments.map((attachment) => ({
|
|
2061
|
+
type: 'image',
|
|
2062
|
+
source: {
|
|
2063
|
+
type: 'base64',
|
|
2064
|
+
media_type: attachment.contentType,
|
|
2065
|
+
data: (0, node_fs_1.readFileSync)(attachment.path).toString('base64'),
|
|
2066
|
+
},
|
|
2067
|
+
})),
|
|
2068
|
+
],
|
|
2069
|
+
},
|
|
2070
|
+
})}\n`,
|
|
2093
2071
|
timeoutMs: (0, runtime_1.readPositiveEnvInt)('PLAYDROP_WORKER_CLAUDE_TIMEOUT_MS', runtime_1.DEFAULT_CODEX_TIMEOUT_MS),
|
|
2094
2072
|
maxOutputChars: runtime_1.DEFAULT_CODEX_LOG_TAIL_CHARS,
|
|
2095
2073
|
onTranscriptChunks: input.onTranscriptChunks,
|
|
@@ -2139,6 +2117,13 @@ function buildAgentRunResult(result) {
|
|
|
2139
2117
|
tokensUsed: result.tokensUsed,
|
|
2140
2118
|
};
|
|
2141
2119
|
}
|
|
2120
|
+
function agentFailureRawError(result) {
|
|
2121
|
+
return [result.stderr, result.stdout, result.outputTail]
|
|
2122
|
+
.map((value) => value.trim())
|
|
2123
|
+
.filter(Boolean)
|
|
2124
|
+
.join('\n')
|
|
2125
|
+
|| 'agent_failure';
|
|
2126
|
+
}
|
|
2142
2127
|
function buildSupervisorFailureRunResult(message) {
|
|
2143
2128
|
return {
|
|
2144
2129
|
exitCode: null,
|
|
@@ -2159,22 +2144,33 @@ function buildSupervisorFailureRunResult(message) {
|
|
|
2159
2144
|
tokensUsed: null,
|
|
2160
2145
|
};
|
|
2161
2146
|
}
|
|
2162
|
-
function
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
if (normalized.startsWith(pluginPrefix)) {
|
|
2166
|
-
return normalized.slice(pluginPrefix.length);
|
|
2147
|
+
function listPluginSkillPaths(pluginRoot) {
|
|
2148
|
+
if (!pluginRoot) {
|
|
2149
|
+
return [];
|
|
2167
2150
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2151
|
+
const skillsRoot = node_path_1.default.join(pluginRoot, 'skills');
|
|
2152
|
+
if (!(0, node_fs_1.existsSync)(skillsRoot)) {
|
|
2153
|
+
return [];
|
|
2154
|
+
}
|
|
2155
|
+
const paths = [];
|
|
2156
|
+
const visit = (directory) => {
|
|
2157
|
+
for (const entry of (0, node_fs_1.readdirSync)(directory, { withFileTypes: true })) {
|
|
2158
|
+
const entryPath = node_path_1.default.join(directory, entry.name);
|
|
2159
|
+
if (entry.isDirectory()) {
|
|
2160
|
+
visit(entryPath);
|
|
2161
|
+
}
|
|
2162
|
+
else if (entry.isFile() && entry.name === 'SKILL.md') {
|
|
2163
|
+
paths.push(node_path_1.default.relative(pluginRoot, entryPath).replace(/\\/g, '/'));
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
};
|
|
2167
|
+
visit(skillsRoot);
|
|
2168
|
+
return paths.sort();
|
|
2172
2169
|
}
|
|
2173
2170
|
function collectObservedPlaydropSkillPaths(output, availablePaths) {
|
|
2174
2171
|
const observed = new Set();
|
|
2175
2172
|
for (const availablePath of availablePaths) {
|
|
2176
|
-
if (output.includes(availablePath)
|
|
2177
|
-
|| output.includes(`${STAGED_PLAYDROP_PLUGIN_ROOT}/${availablePath}`)) {
|
|
2173
|
+
if (output.includes(availablePath)) {
|
|
2178
2174
|
observed.add(availablePath);
|
|
2179
2175
|
}
|
|
2180
2176
|
}
|
|
@@ -2195,8 +2191,8 @@ async function recordAgentRunTelemetry(input) {
|
|
|
2195
2191
|
workerKey: input.workerKey,
|
|
2196
2192
|
leaseToken: input.leaseToken,
|
|
2197
2193
|
attempt: input.task.attempts,
|
|
2198
|
-
agent: input.assignment.agent,
|
|
2199
|
-
requestedModel: input.assignment.model,
|
|
2194
|
+
agent: input.assignment.agent.runtime,
|
|
2195
|
+
requestedModel: input.assignment.agent.model,
|
|
2200
2196
|
resolvedRuntimeModel: input.resolvedRuntimeModel,
|
|
2201
2197
|
reasoningEffort: input.reasoningEffort,
|
|
2202
2198
|
status: resolveTelemetryStatus(input.status),
|
|
@@ -2216,6 +2212,11 @@ async function recordAgentRunTelemetry(input) {
|
|
|
2216
2212
|
observedSkillPaths: [...observedSkillPaths].sort(),
|
|
2217
2213
|
startedAt: input.startedAt.toISOString(),
|
|
2218
2214
|
completedAt: input.completedAt.toISOString(),
|
|
2215
|
+
...(input.status === 'FAILED'
|
|
2216
|
+
? {
|
|
2217
|
+
terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(agentFailureRawError(input.result), 'agent'),
|
|
2218
|
+
}
|
|
2219
|
+
: {}),
|
|
2219
2220
|
});
|
|
2220
2221
|
}
|
|
2221
2222
|
function providerNameForAgent(agent) {
|
|
@@ -2232,9 +2233,14 @@ function extractProviderStatusCode(text) {
|
|
|
2232
2233
|
}
|
|
2233
2234
|
return null;
|
|
2234
2235
|
}
|
|
2235
|
-
function buildAgentFailureCode(agent, result) {
|
|
2236
|
+
function buildAgentFailureCode(agent, result, request) {
|
|
2236
2237
|
const provider = providerNameForAgent(agent);
|
|
2237
2238
|
const combinedOutput = `${result.outputTail}\n${result.stderr}\n${result.stdout}`;
|
|
2239
|
+
if (request
|
|
2240
|
+
&& (/\b(?:unknown|invalid|unsupported)\s+(?:model|effort)\b/i.test(combinedOutput)
|
|
2241
|
+
|| /\b(?:model|effort)\b.{0,80}\b(?:not found|not available|does not exist|is unavailable|is not supported)\b/i.test(combinedOutput))) {
|
|
2242
|
+
return `requested_model_unavailable:${agent}:${request.model}:${request.reasoningEffort ?? ''}`;
|
|
2243
|
+
}
|
|
2238
2244
|
const providerStatus = extractProviderStatusCode(combinedOutput);
|
|
2239
2245
|
if (providerStatus === 529 || (/\b529\b/.test(combinedOutput) && /\boverloaded?\b/i.test(combinedOutput))) {
|
|
2240
2246
|
return `agent_provider_overloaded:${provider}:529`;
|
|
@@ -2255,6 +2261,9 @@ function buildAgentFailureCode(agent, result) {
|
|
|
2255
2261
|
}
|
|
2256
2262
|
function describeAgentFailureForEvent(errorCode) {
|
|
2257
2263
|
const [code, provider, detail] = errorCode.split(':');
|
|
2264
|
+
if (code === 'requested_model_unavailable') {
|
|
2265
|
+
return 'The requested model or reasoning effort is not available on this worker. Choose another exact configuration and retry.';
|
|
2266
|
+
}
|
|
2258
2267
|
const providerLabel = provider === 'claude' ? 'Claude' : provider === 'codex' ? 'Codex' : 'The agent provider';
|
|
2259
2268
|
if (code === 'agent_provider_overloaded') {
|
|
2260
2269
|
return detail
|
|
@@ -2681,14 +2690,6 @@ function buildWorkerSetupActions(input) {
|
|
|
2681
2690
|
message: 'The PlayDrop CLI package could not load playwright-core from its installed dependencies.',
|
|
2682
2691
|
});
|
|
2683
2692
|
}
|
|
2684
|
-
if (reasons.has('playdrop_plugin_staging_manifest_missing')) {
|
|
2685
|
-
actions.push({
|
|
2686
|
-
reason: 'playdrop_plugin_staging_manifest_missing',
|
|
2687
|
-
label: 'Install or update the PlayDrop agent plugin',
|
|
2688
|
-
command: 'playdrop agents status --json',
|
|
2689
|
-
message: 'Run the CLI-reported nextAction.command for a supported local agent so playdrop-worker-staging.json is available.',
|
|
2690
|
-
});
|
|
2691
|
-
}
|
|
2692
2693
|
if (reasons.has('agent_cli_not_found')) {
|
|
2693
2694
|
actions.push({
|
|
2694
2695
|
reason: 'agent_cli_not_found',
|
|
@@ -3559,8 +3560,14 @@ async function setupWorker(options = {}) {
|
|
|
3559
3560
|
}, { env: options.env });
|
|
3560
3561
|
}
|
|
3561
3562
|
async function failTaskWithRetry(client, taskId, body) {
|
|
3563
|
+
const terminalReason = body.terminalReason ?? (0, types_1.classifyAgentTaskTerminalReason)(body.error, 'worker');
|
|
3564
|
+
const requestBody = {
|
|
3565
|
+
...body,
|
|
3566
|
+
error: terminalReason.code,
|
|
3567
|
+
terminalReason,
|
|
3568
|
+
};
|
|
3562
3569
|
try {
|
|
3563
|
-
await client.workerFailAgentTask(taskId,
|
|
3570
|
+
await client.workerFailAgentTask(taskId, requestBody);
|
|
3564
3571
|
}
|
|
3565
3572
|
catch (firstError) {
|
|
3566
3573
|
if (isAgentTaskNotRunningError(firstError)) {
|
|
@@ -3568,7 +3575,7 @@ async function failTaskWithRetry(client, taskId, body) {
|
|
|
3568
3575
|
}
|
|
3569
3576
|
await sleep(FAIL_REPORT_RETRY_DELAY_MS);
|
|
3570
3577
|
try {
|
|
3571
|
-
await client.workerFailAgentTask(taskId,
|
|
3578
|
+
await client.workerFailAgentTask(taskId, requestBody);
|
|
3572
3579
|
}
|
|
3573
3580
|
catch (secondError) {
|
|
3574
3581
|
if (isAgentTaskNotRunningError(secondError)) {
|
|
@@ -3588,13 +3595,24 @@ async function fetchTaskDetail(client, target, taskId) {
|
|
|
3588
3595
|
return await client.getAgentTask(taskId);
|
|
3589
3596
|
}
|
|
3590
3597
|
function instrumentTaskChromeTitleMatchers(assignment) {
|
|
3591
|
-
const
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3598
|
+
const playdrop = assignment.task.metadata.playdrop;
|
|
3599
|
+
const metadata = playdrop && typeof playdrop === 'object' && !Array.isArray(playdrop)
|
|
3600
|
+
? playdrop
|
|
3601
|
+
: {};
|
|
3602
|
+
const kind = metadata.kind;
|
|
3603
|
+
const evalTarget = metadata.evalTarget && typeof metadata.evalTarget === 'object' && !Array.isArray(metadata.evalTarget)
|
|
3604
|
+
? metadata.evalTarget
|
|
3605
|
+
: null;
|
|
3606
|
+
const targets = kind === 'GAME_REVIEW'
|
|
3607
|
+
? [metadata.reviewTarget]
|
|
3608
|
+
: kind === 'GAME_EVAL' && Array.isArray(evalTarget?.targets)
|
|
3609
|
+
? evalTarget.targets
|
|
3595
3610
|
: [];
|
|
3596
3611
|
const matchers = new Set();
|
|
3597
|
-
for (const
|
|
3612
|
+
for (const rawTarget of targets) {
|
|
3613
|
+
const target = rawTarget && typeof rawTarget === 'object' && !Array.isArray(rawTarget)
|
|
3614
|
+
? rawTarget
|
|
3615
|
+
: null;
|
|
3598
3616
|
if (!target)
|
|
3599
3617
|
continue;
|
|
3600
3618
|
for (const value of [target.appDisplayName, target.appName]) {
|
|
@@ -3687,7 +3705,7 @@ on run matchers
|
|
|
3687
3705
|
end run
|
|
3688
3706
|
`;
|
|
3689
3707
|
async function enforceInstrumentBrowserHygiene(assignment, options = {}) {
|
|
3690
|
-
if (assignment.
|
|
3708
|
+
if (assignment.agent.browser === 'NONE') {
|
|
3691
3709
|
return { remainingTaskTabs: 0, blankWindows: 0 };
|
|
3692
3710
|
}
|
|
3693
3711
|
if ((options.platform ?? node_process_1.default.platform) !== 'darwin') {
|
|
@@ -3895,11 +3913,19 @@ async function startWorker(options = {}) {
|
|
|
3895
3913
|
});
|
|
3896
3914
|
}, HEARTBEAT_INTERVAL_MS);
|
|
3897
3915
|
const runClaimedTask = async (claim, devPort) => {
|
|
3898
|
-
const
|
|
3899
|
-
if (!
|
|
3900
|
-
throw new Error('
|
|
3916
|
+
const claimedAssignment = normalizeWorkerTaskAssignmentV2(claim);
|
|
3917
|
+
if (!claimedAssignment) {
|
|
3918
|
+
throw new Error('agent_task_claim_missing_assignment');
|
|
3901
3919
|
}
|
|
3902
|
-
const
|
|
3920
|
+
const taskContext = buildWorkerTaskContextV2(claimedAssignment);
|
|
3921
|
+
const task = {
|
|
3922
|
+
id: claimedAssignment.task.id,
|
|
3923
|
+
kind: taskContext.kind,
|
|
3924
|
+
model: claimedAssignment.agent.model,
|
|
3925
|
+
executionTarget: claimedAssignment.task.executionTarget,
|
|
3926
|
+
attempts: claimedAssignment.task.attempt,
|
|
3927
|
+
};
|
|
3928
|
+
const leaseToken = claim.action === 'run' ? claim.lease.token.trim() : '';
|
|
3903
3929
|
if (!leaseToken) {
|
|
3904
3930
|
throw new Error('agent_task_claim_missing_lease_token');
|
|
3905
3931
|
}
|
|
@@ -3914,7 +3940,7 @@ async function startWorker(options = {}) {
|
|
|
3914
3940
|
let workspaceDir = null;
|
|
3915
3941
|
let availableSkillPaths = [];
|
|
3916
3942
|
const observedSkillPaths = new Set();
|
|
3917
|
-
|
|
3943
|
+
const assignment = claimedAssignment;
|
|
3918
3944
|
let resolvedRuntimeModel = task.model;
|
|
3919
3945
|
let reasoningEffort = null;
|
|
3920
3946
|
let agentStartedAt = null;
|
|
@@ -3923,9 +3949,6 @@ async function startWorker(options = {}) {
|
|
|
3923
3949
|
let telemetryReported = false;
|
|
3924
3950
|
let heartbeatTransientFailures = 0;
|
|
3925
3951
|
const reportTelemetry = async (status, setupError) => {
|
|
3926
|
-
if (!assignment) {
|
|
3927
|
-
return;
|
|
3928
|
-
}
|
|
3929
3952
|
const completedAt = agentCompletedAt ?? new Date();
|
|
3930
3953
|
await recordAgentRunTelemetry({
|
|
3931
3954
|
client,
|
|
@@ -3945,20 +3968,19 @@ async function startWorker(options = {}) {
|
|
|
3945
3968
|
telemetryReported = true;
|
|
3946
3969
|
};
|
|
3947
3970
|
try {
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
resolvedRuntimeModel = codexModel?.model ?? claudeModel?.model ?? assignment.model;
|
|
3971
|
+
const codexModel = assignment.agent.runtime === 'CODEX'
|
|
3972
|
+
? resolveCodexModel(assignment.agent.model, assignment.agent.reasoningEffort)
|
|
3973
|
+
: null;
|
|
3974
|
+
const claudeModel = assignment.agent.runtime === 'CLAUDE_CODE'
|
|
3975
|
+
? resolveClaudeModel(assignment.agent.model, assignment.agent.reasoningEffort)
|
|
3976
|
+
: null;
|
|
3977
|
+
resolvedRuntimeModel = codexModel?.model ?? claudeModel?.model ?? assignment.agent.model;
|
|
3955
3978
|
reasoningEffort = codexModel?.reasoningEffort ?? claudeModel?.effort ?? null;
|
|
3956
|
-
const taskContext = buildWorkerTaskContext(claim, assignment);
|
|
3957
3979
|
workspaceDir = workerTaskWorkspaceDirFromAssignment(assignment);
|
|
3958
3980
|
await pruneWorkerTaskWorkspaces(workspaceDir);
|
|
3959
3981
|
await (0, promises_1.rm)(workspaceDir, { recursive: true, force: true });
|
|
3960
3982
|
await (0, promises_1.mkdir)(workspaceDir, { recursive: true });
|
|
3961
|
-
const eventDir = node_path_1.default.join(workspaceDir, '.playdrop
|
|
3983
|
+
const eventDir = node_path_1.default.join(workspaceDir, '.playdrop', 'task-events');
|
|
3962
3984
|
await (0, promises_1.mkdir)(eventDir, { recursive: true });
|
|
3963
3985
|
await stageWorkerTaskContext({
|
|
3964
3986
|
workspaceDir,
|
|
@@ -4003,52 +4025,16 @@ async function startWorker(options = {}) {
|
|
|
4003
4025
|
appendObservedSkillPathsFromTranscript(chunks);
|
|
4004
4026
|
await appendWorkerTranscriptChunks({ client, taskId: task.id, workerKey, leaseToken, chunks });
|
|
4005
4027
|
};
|
|
4006
|
-
await
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
pluginBundle: assignment.pluginBundle,
|
|
4017
|
-
});
|
|
4018
|
-
const stagedPluginPaths = await stagePlaydropPluginReferences({
|
|
4019
|
-
workspaceDir,
|
|
4020
|
-
pluginRoot: assignmentPluginRoot,
|
|
4021
|
-
kind: task.kind,
|
|
4022
|
-
});
|
|
4023
|
-
if (assignment.agent === 'CODEX') {
|
|
4024
|
-
await stageCodexTaskSkills({ workspaceDir, stagedPluginPaths });
|
|
4025
|
-
}
|
|
4026
|
-
availableSkillPaths = normalizeTelemetrySkillPaths(stagedPluginPaths);
|
|
4027
|
-
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4028
|
-
workerKey,
|
|
4029
|
-
leaseToken,
|
|
4030
|
-
kind: 'progress',
|
|
4031
|
-
phase: 'setup',
|
|
4032
|
-
message: devPluginWorkingTree
|
|
4033
|
-
? 'Staged PlayDrop plugin references from explicit dev working tree'
|
|
4034
|
-
: 'Staged PlayDrop plugin references from server task bundle',
|
|
4035
|
-
pct: 3,
|
|
4036
|
-
payload: {
|
|
4037
|
-
pluginSource: devPluginWorkingTree ? 'DEV_WORKING_TREE' : 'SERVER_TASK_BUNDLE',
|
|
4038
|
-
bundleSha: assignment.pluginBundle.sha256,
|
|
4039
|
-
},
|
|
4040
|
-
});
|
|
4041
|
-
if (assignment.workspace.files.length > 0) {
|
|
4042
|
-
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4043
|
-
workerKey,
|
|
4044
|
-
leaseToken,
|
|
4045
|
-
kind: 'progress',
|
|
4046
|
-
phase: 'setup',
|
|
4047
|
-
message: `Staging ${assignment.workspace.files.length} server-provided workspace file${assignment.workspace.files.length === 1 ? '' : 's'}`,
|
|
4048
|
-
pct: 3,
|
|
4049
|
-
});
|
|
4050
|
-
await stageAssignmentWorkspaceFiles(workspaceDir, assignment.workspace.files);
|
|
4051
|
-
}
|
|
4028
|
+
await stageAssignmentWorkspaceV2(workspaceDir, assignment.workspace);
|
|
4029
|
+
const assignmentPluginRoot = devPluginWorkingTree
|
|
4030
|
+
? await stageAgentBundleWorkspaceFiles(devPluginWorkingTree, workspaceDir)
|
|
4031
|
+
: assignment.plugin
|
|
4032
|
+
? await stageAssignmentPluginBundle({
|
|
4033
|
+
workspaceDir,
|
|
4034
|
+
pluginBundle: assignment.plugin,
|
|
4035
|
+
})
|
|
4036
|
+
: null;
|
|
4037
|
+
availableSkillPaths = listPluginSkillPaths(assignmentPluginRoot);
|
|
4052
4038
|
heartbeatTimer = setInterval(() => {
|
|
4053
4039
|
client.workerHeartbeatAgentTask(task.id, { workerKey, leaseToken })
|
|
4054
4040
|
.then((heartbeat) => {
|
|
@@ -4107,57 +4093,18 @@ async function startWorker(options = {}) {
|
|
|
4107
4093
|
handleEventDrainFailure(error);
|
|
4108
4094
|
});
|
|
4109
4095
|
}, 1000);
|
|
4110
|
-
const prompt = assignment.prompt;
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
kind: 'progress',
|
|
4118
|
-
phase: 'setup',
|
|
4119
|
-
message: `Staging base source ${baseSource.appName} version ${baseSource.version} into the workspace`,
|
|
4120
|
-
pct: 4,
|
|
4121
|
-
});
|
|
4122
|
-
await stageWorkerBaseAppSource({
|
|
4123
|
-
client,
|
|
4124
|
-
workspaceDir,
|
|
4125
|
-
creatorUsername: baseSourcePayload.creatorUsername,
|
|
4126
|
-
baseApp: baseSource,
|
|
4127
|
-
});
|
|
4128
|
-
}
|
|
4129
|
-
else if (task.kind === 'NEW_GAME' || task.kind === 'REMIX_GAME') {
|
|
4130
|
-
if (assignment.inputs.baseSource !== null) {
|
|
4131
|
-
throw new Error('agent_task_assignment_unexpected_base_source');
|
|
4132
|
-
}
|
|
4133
|
-
if (task.kind === 'REMIX_GAME' && !assignment.inputs.remixSource) {
|
|
4134
|
-
throw new Error('agent_task_assignment_remix_source_missing');
|
|
4135
|
-
}
|
|
4136
|
-
}
|
|
4137
|
-
else if (task.kind === 'GAME_REVIEW') {
|
|
4138
|
-
if (assignment.inputs.baseSource !== null) {
|
|
4139
|
-
throw new Error('agent_task_assignment_unexpected_base_source');
|
|
4140
|
-
}
|
|
4141
|
-
if (!assignment.inputs.reviewTarget) {
|
|
4142
|
-
throw new Error('agent_task_assignment_review_target_missing');
|
|
4143
|
-
}
|
|
4144
|
-
}
|
|
4145
|
-
else if (task.kind === 'GAME_EVAL') {
|
|
4146
|
-
if (assignment.inputs.baseSource !== null) {
|
|
4147
|
-
throw new Error('agent_task_assignment_unexpected_base_source');
|
|
4148
|
-
}
|
|
4149
|
-
if (!assignment.inputs.evalTarget) {
|
|
4150
|
-
throw new Error('agent_task_assignment_eval_target_missing');
|
|
4151
|
-
}
|
|
4152
|
-
}
|
|
4153
|
-
else {
|
|
4154
|
-
throw new Error(`unsupported_agent_task_kind:${task.kind}`);
|
|
4155
|
-
}
|
|
4096
|
+
const prompt = assignment.request.prompt;
|
|
4097
|
+
const imageAttachments = assignment.request.attachments
|
|
4098
|
+
.filter((attachment) => attachment.contentType.startsWith('image/'))
|
|
4099
|
+
.map((attachment) => ({
|
|
4100
|
+
path: resolveWorkspaceFileDestination(workspaceDir, attachment.path),
|
|
4101
|
+
contentType: attachment.contentType,
|
|
4102
|
+
}));
|
|
4156
4103
|
if (shuttingDown) {
|
|
4157
4104
|
throw new Error('worker_shutdown');
|
|
4158
4105
|
}
|
|
4159
4106
|
agentStartedAt = new Date();
|
|
4160
|
-
if (assignment.agent === 'CODEX') {
|
|
4107
|
+
if (assignment.agent.runtime === 'CODEX') {
|
|
4161
4108
|
agentResult = await runCodex({
|
|
4162
4109
|
workspaceDir,
|
|
4163
4110
|
binDir,
|
|
@@ -4166,9 +4113,12 @@ async function startWorker(options = {}) {
|
|
|
4166
4113
|
envName: env,
|
|
4167
4114
|
workerUsername: username,
|
|
4168
4115
|
taskId: task.id,
|
|
4116
|
+
taskToken: assignment.task.token,
|
|
4169
4117
|
attempt: taskContext.attempt,
|
|
4170
4118
|
devPort,
|
|
4171
|
-
codexModel: codexModel ?? resolveCodexModel(assignment.model),
|
|
4119
|
+
codexModel: codexModel ?? resolveCodexModel(assignment.agent.model),
|
|
4120
|
+
taskPluginRoot: assignmentPluginRoot,
|
|
4121
|
+
imagePaths: imageAttachments.map((attachment) => attachment.path),
|
|
4172
4122
|
onTranscriptChunks: async (chunks) => {
|
|
4173
4123
|
await appendTranscriptChunks(chunks);
|
|
4174
4124
|
},
|
|
@@ -4177,21 +4127,23 @@ async function startWorker(options = {}) {
|
|
|
4177
4127
|
},
|
|
4178
4128
|
});
|
|
4179
4129
|
}
|
|
4180
|
-
else if (assignment.agent === 'CLAUDE_CODE') {
|
|
4130
|
+
else if (assignment.agent.runtime === 'CLAUDE_CODE') {
|
|
4181
4131
|
agentResult = await runClaude({
|
|
4182
4132
|
workspaceDir,
|
|
4183
4133
|
binDir,
|
|
4184
4134
|
eventDir,
|
|
4185
4135
|
prompt,
|
|
4186
|
-
enableChrome:
|
|
4187
|
-
enablePlaywrightMcp:
|
|
4136
|
+
enableChrome: assignment.agent.browser === 'CHROME',
|
|
4137
|
+
enablePlaywrightMcp: assignment.agent.browser === 'PLAYWRIGHT',
|
|
4188
4138
|
envName: env,
|
|
4189
4139
|
workerUsername: username,
|
|
4190
4140
|
taskId: task.id,
|
|
4141
|
+
taskToken: assignment.task.token,
|
|
4191
4142
|
attempt: taskContext.attempt,
|
|
4192
4143
|
devPort,
|
|
4193
|
-
claudeModel: claudeModel ?? resolveClaudeModel(assignment.model),
|
|
4144
|
+
claudeModel: claudeModel ?? resolveClaudeModel(assignment.agent.model),
|
|
4194
4145
|
taskPluginRoot: assignmentPluginRoot,
|
|
4146
|
+
imageAttachments,
|
|
4195
4147
|
onTranscriptChunks: async (chunks) => {
|
|
4196
4148
|
await appendTranscriptChunks(chunks);
|
|
4197
4149
|
},
|
|
@@ -4200,17 +4152,11 @@ async function startWorker(options = {}) {
|
|
|
4200
4152
|
},
|
|
4201
4153
|
});
|
|
4202
4154
|
}
|
|
4203
|
-
else if (assignment.agent === 'CURSOR_COMPOSER') {
|
|
4204
|
-
const assignmentAgent = assignment.agent;
|
|
4205
|
-
const assignmentModel = assignment.model;
|
|
4206
|
-
const plannedAttemptIndex = assignment.attemptPlan?.options.findIndex((option) => option.agent === assignmentAgent && option.model === assignmentModel);
|
|
4207
|
-
const attemptIndex = typeof plannedAttemptIndex === 'number' && plannedAttemptIndex >= 0
|
|
4208
|
-
? plannedAttemptIndex
|
|
4209
|
-
: 0;
|
|
4155
|
+
else if (assignment.agent.runtime === 'CURSOR_COMPOSER') {
|
|
4210
4156
|
await client.workerRecordAgentTaskAttemptUnavailable(task.id, {
|
|
4211
4157
|
workerKey,
|
|
4212
4158
|
leaseToken,
|
|
4213
|
-
attemptIndex,
|
|
4159
|
+
attemptIndex: 0,
|
|
4214
4160
|
reason: 'unavailable_runtime',
|
|
4215
4161
|
message: 'Cursor Composer noninteractive worker execution is not configured on this worker.',
|
|
4216
4162
|
});
|
|
@@ -4218,7 +4164,7 @@ async function startWorker(options = {}) {
|
|
|
4218
4164
|
throw new Error('worker_cursor_composer_runner_not_configured');
|
|
4219
4165
|
}
|
|
4220
4166
|
else {
|
|
4221
|
-
throw new Error(`unsupported_worker_agent:${assignment.agent}`);
|
|
4167
|
+
throw new Error(`unsupported_worker_agent:${assignment.agent.runtime}`);
|
|
4222
4168
|
}
|
|
4223
4169
|
agentCompletedAt = new Date();
|
|
4224
4170
|
await queueEventDrain().catch((error) => {
|
|
@@ -4277,7 +4223,10 @@ async function startWorker(options = {}) {
|
|
|
4277
4223
|
}
|
|
4278
4224
|
else {
|
|
4279
4225
|
retainWorkspace = true;
|
|
4280
|
-
const failureCode = buildAgentFailureCode(assignment.agent, completedAgentResult
|
|
4226
|
+
const failureCode = buildAgentFailureCode(assignment.agent.runtime, completedAgentResult, {
|
|
4227
|
+
model: assignment.agent.model,
|
|
4228
|
+
reasoningEffort,
|
|
4229
|
+
});
|
|
4281
4230
|
await client.workerCreateAgentTaskEvent(task.id, {
|
|
4282
4231
|
workerKey,
|
|
4283
4232
|
leaseToken,
|
|
@@ -4291,6 +4240,7 @@ async function startWorker(options = {}) {
|
|
|
4291
4240
|
workerKey,
|
|
4292
4241
|
leaseToken,
|
|
4293
4242
|
error: failureCode,
|
|
4243
|
+
terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(agentFailureRawError(completedAgentResult), 'agent'),
|
|
4294
4244
|
result: agentRunResult,
|
|
4295
4245
|
});
|
|
4296
4246
|
await reportTelemetry('FAILED');
|
|
@@ -4377,6 +4327,7 @@ async function startWorker(options = {}) {
|
|
|
4377
4327
|
workerKey,
|
|
4378
4328
|
leaseToken,
|
|
4379
4329
|
error: normalizeWorkerFailureErrorCode(message),
|
|
4330
|
+
terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(message, 'worker'),
|
|
4380
4331
|
result: { workerError: message },
|
|
4381
4332
|
});
|
|
4382
4333
|
}
|
|
@@ -4399,9 +4350,8 @@ async function startWorker(options = {}) {
|
|
|
4399
4350
|
if (eventDrainTimer) {
|
|
4400
4351
|
clearInterval(eventDrainTimer);
|
|
4401
4352
|
}
|
|
4402
|
-
if (
|
|
4403
|
-
&&
|
|
4404
|
-
&& (assignment.kind === 'GAME_REVIEW' || assignment.kind === 'GAME_EVAL')) {
|
|
4353
|
+
if (agentStartedAt
|
|
4354
|
+
&& assignment.agent.browser !== 'NONE') {
|
|
4405
4355
|
try {
|
|
4406
4356
|
const hygiene = await enforceInstrumentBrowserHygiene(assignment);
|
|
4407
4357
|
console.log(`Agent task ${task.id} browser hygiene passed: ${hygiene.remainingTaskTabs} task tabs, ${hygiene.blankWindows} blank window(s).`);
|
|
@@ -4475,8 +4425,7 @@ async function startWorker(options = {}) {
|
|
|
4475
4425
|
claimBackoffMs = nextClaimBackoffMs(claimBackoffMs);
|
|
4476
4426
|
continue;
|
|
4477
4427
|
}
|
|
4478
|
-
|
|
4479
|
-
if (!task) {
|
|
4428
|
+
if (claim.action !== 'run') {
|
|
4480
4429
|
if (shuttingDown || quiescingForUpdate) {
|
|
4481
4430
|
break;
|
|
4482
4431
|
}
|
|
@@ -4486,18 +4435,19 @@ async function startWorker(options = {}) {
|
|
|
4486
4435
|
await sleep(DEFAULT_POLL_INTERVAL_MS);
|
|
4487
4436
|
continue;
|
|
4488
4437
|
}
|
|
4489
|
-
|
|
4438
|
+
const taskId = claim.assignment.task.id;
|
|
4439
|
+
activeTaskIds.add(taskId);
|
|
4490
4440
|
activeDevPorts.add(devPort);
|
|
4491
4441
|
const taskRun = runClaimedTask(claim, devPort)
|
|
4492
4442
|
.catch((error) => {
|
|
4493
4443
|
recordFatalTaskError(error);
|
|
4494
4444
|
})
|
|
4495
4445
|
.finally(() => {
|
|
4496
|
-
activeTaskRuns.delete(
|
|
4497
|
-
activeTaskIds.delete(
|
|
4446
|
+
activeTaskRuns.delete(taskId);
|
|
4447
|
+
activeTaskIds.delete(taskId);
|
|
4498
4448
|
activeDevPorts.delete(devPort);
|
|
4499
4449
|
});
|
|
4500
|
-
activeTaskRuns.set(
|
|
4450
|
+
activeTaskRuns.set(taskId, taskRun);
|
|
4501
4451
|
if (options.once) {
|
|
4502
4452
|
await taskRun;
|
|
4503
4453
|
if (fatalTaskError) {
|
|
@@ -4594,6 +4544,21 @@ async function reportTask(options) {
|
|
|
4594
4544
|
}
|
|
4595
4545
|
resolveWorkerTaskStateForWrite();
|
|
4596
4546
|
}
|
|
4547
|
+
function showTaskContext() {
|
|
4548
|
+
const taskContext = readTaskContextFile();
|
|
4549
|
+
console.log(JSON.stringify({
|
|
4550
|
+
task: {
|
|
4551
|
+
id: taskContext.taskId,
|
|
4552
|
+
type: taskContext.taskType,
|
|
4553
|
+
attempt: taskContext.attempt,
|
|
4554
|
+
executionTarget: taskContext.target,
|
|
4555
|
+
metadata: taskContext.metadata,
|
|
4556
|
+
},
|
|
4557
|
+
request: {
|
|
4558
|
+
prompt: taskContext.creatorRequest,
|
|
4559
|
+
},
|
|
4560
|
+
}, null, 2));
|
|
4561
|
+
}
|
|
4597
4562
|
async function reportCatalogueTask(options) {
|
|
4598
4563
|
const message = options.message?.trim();
|
|
4599
4564
|
if (!message) {
|
|
@@ -4629,11 +4594,26 @@ async function resolveTaskCommandContext(command, optionsEnv, taskContext) {
|
|
|
4629
4594
|
if (!ctx) {
|
|
4630
4595
|
return null;
|
|
4631
4596
|
}
|
|
4632
|
-
const
|
|
4597
|
+
const taskClientOptions = {
|
|
4598
|
+
token: ctx.token,
|
|
4599
|
+
onBehalfCreatorUsername: taskContext.creatorUsername,
|
|
4600
|
+
agentTaskToken: taskContext.taskToken,
|
|
4601
|
+
agentTaskId: taskContext.taskId,
|
|
4602
|
+
agentTaskAttempt: taskContext.attempt,
|
|
4603
|
+
};
|
|
4604
|
+
const client = (0, apiClient_1.createCliApiClient)({
|
|
4605
|
+
...taskClientOptions,
|
|
4606
|
+
baseUrl: ctx.envConfig.apiBase,
|
|
4607
|
+
});
|
|
4608
|
+
const aiClient = (0, apiClient_1.createCliAiClient)({
|
|
4609
|
+
...taskClientOptions,
|
|
4610
|
+
baseUrl: ctx.envConfig.aiBase,
|
|
4611
|
+
});
|
|
4612
|
+
const me = await client.me();
|
|
4633
4613
|
if (!me.user) {
|
|
4634
4614
|
throw new Error('task_context_user_missing');
|
|
4635
4615
|
}
|
|
4636
|
-
return { ...ctx, user: me.user };
|
|
4616
|
+
return { ...ctx, client, aiClient, user: me.user };
|
|
4637
4617
|
}
|
|
4638
4618
|
async function uploadTask(options = {}) {
|
|
4639
4619
|
const taskContext = readTaskContextFile();
|
|
@@ -4772,6 +4752,7 @@ async function completeTask(options) {
|
|
|
4772
4752
|
await ctx.client.workerFailAgentTask(taskContext.taskId, {
|
|
4773
4753
|
taskToken: taskContext.taskToken,
|
|
4774
4754
|
error: 'remix_relation_missing',
|
|
4755
|
+
terminalReason: (0, types_1.classifyAgentTaskTerminalReason)('upload_validation:remix_relation_missing', 'agent'),
|
|
4775
4756
|
result: {
|
|
4776
4757
|
appId: uploadResult.appId,
|
|
4777
4758
|
appVersionId: uploadResult.appVersionId,
|
|
@@ -5035,6 +5016,7 @@ async function instrumentErrorTask(options) {
|
|
|
5035
5016
|
await ctx.client.workerFailAgentTask(taskContext.taskId, {
|
|
5036
5017
|
taskToken: taskContext.taskToken,
|
|
5037
5018
|
error: `instrument_error:${reason}`,
|
|
5019
|
+
terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(`instrument_error:${reason}`, 'instrument'),
|
|
5038
5020
|
result: {
|
|
5039
5021
|
outcome: 'instrument_error',
|
|
5040
5022
|
reason,
|
|
@@ -5058,6 +5040,7 @@ async function failTask(options) {
|
|
|
5058
5040
|
await ctx.client.workerFailAgentTask(taskContext.taskId, {
|
|
5059
5041
|
taskToken: taskContext.taskToken,
|
|
5060
5042
|
error: message,
|
|
5043
|
+
terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(message, 'agent'),
|
|
5061
5044
|
result: { failedBy: 'agent' },
|
|
5062
5045
|
});
|
|
5063
5046
|
(0, output_1.printSuccess)('Task marked failed. This was the final operation; send one short final status sentence and stop immediately.');
|