@playdrop/playdrop-cli 0.10.17 → 0.10.19
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 +1 -1
- package/dist/apps/index.js +2 -2
- package/dist/apps/registration.js +1 -0
- package/dist/captureRuntime.d.ts +7 -0
- package/dist/captureRuntime.js +36 -0
- package/dist/commands/captureRemote.d.ts +2 -0
- package/dist/commands/captureRemote.js +42 -2
- package/dist/commands/upgrade.js +7 -2
- package/dist/commands/worker/runtime.d.ts +7 -0
- package/dist/commands/worker/runtime.js +171 -31
- package/dist/commands/worker.d.ts +16 -3
- package/dist/commands/worker.js +614 -132
- package/dist/index.js +18 -3
- package/node_modules/@playdrop/api-client/dist/client.d.ts +3 -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 +3 -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 +28 -1
- package/node_modules/@playdrop/api-client/dist/index.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +10 -0
- package/node_modules/@playdrop/config/client-meta.json +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +86 -3
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +28 -1
- package/package.json +1 -1
package/dist/commands/worker.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = exports.WORKER_SESSION_EXPIRED_MESSAGE = exports.runLoggedProcess = exports.readCodexSandboxMode = exports.readEnvBoolean = exports.extractCodexTokensUsed = exports.extractAgentTokenUsage = exports.buildWorkerChildEnv = exports.buildClaudePermissionSettings = exports.buildClaudeExecArgs = exports.buildCodexExecArgs = exports.assertWorkerTokenUsageWithinCap = exports.DEFAULT_WORKER_TOKEN_CAP = exports.DEFAULT_CODEX_TIMEOUT_MS = void 0;
|
|
6
|
+
exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = exports.WORKER_SESSION_EXPIRED_MESSAGE = exports.runLoggedProcess = exports.readCodexRolloutTokenUsageForThread = exports.readCodexSandboxMode = exports.readEnvBoolean = exports.extractCodexTokensUsed = exports.extractCodexThreadId = exports.extractCodexRolloutTokenUsage = exports.extractAgentTokenUsage = exports.buildWorkerChildEnv = exports.buildClaudePermissionSettings = exports.buildClaudeExecArgs = exports.buildCodexExecArgs = exports.assertWorkerTokenUsageWithinCap = exports.DEFAULT_WORKER_TOKEN_CAP = exports.DEFAULT_CODEX_TIMEOUT_MS = void 0;
|
|
7
7
|
exports.allocateWorkerDevPort = allocateWorkerDevPort;
|
|
8
8
|
exports.isWorkerContextCommandAllowed = isWorkerContextCommandAllowed;
|
|
9
9
|
exports.resolveWorkerExecutionTargetFromRole = resolveWorkerExecutionTargetFromRole;
|
|
@@ -13,6 +13,7 @@ exports.claimBackoffDelayMs = claimBackoffDelayMs;
|
|
|
13
13
|
exports.resolveWorkerHomeDir = resolveWorkerHomeDir;
|
|
14
14
|
exports.hasAgentTaskUploadedArtifact = hasAgentTaskUploadedArtifact;
|
|
15
15
|
exports.appendWorkerTranscriptChunks = appendWorkerTranscriptChunks;
|
|
16
|
+
exports.readTaskNextStepsFile = readTaskNextStepsFile;
|
|
16
17
|
exports.assertTaskUploadResultMatchesContext = assertTaskUploadResultMatchesContext;
|
|
17
18
|
exports.stageAssignmentWorkspaceFiles = stageAssignmentWorkspaceFiles;
|
|
18
19
|
exports.resolvePlaydropPluginRoot = resolvePlaydropPluginRoot;
|
|
@@ -22,6 +23,8 @@ exports.readWorkerTaskState = readWorkerTaskState;
|
|
|
22
23
|
exports.buildWorkerHealthAlertText = buildWorkerHealthAlertText;
|
|
23
24
|
exports.isWorkerAuthFailureError = isWorkerAuthFailureError;
|
|
24
25
|
exports.classifyWorkerEventDrainError = classifyWorkerEventDrainError;
|
|
26
|
+
exports.isWorkerTransientNetworkError = isWorkerTransientNetworkError;
|
|
27
|
+
exports.classifyWorkerHeartbeatError = classifyWorkerHeartbeatError;
|
|
25
28
|
exports.assertWorkerClaimErrorRetryable = assertWorkerClaimErrorRetryable;
|
|
26
29
|
exports.resolvePlaydropAssetRequirementFromText = resolvePlaydropAssetRequirementFromText;
|
|
27
30
|
exports.loadWorkerEnvFile = loadWorkerEnvFile;
|
|
@@ -44,6 +47,7 @@ exports.startWorker = startWorker;
|
|
|
44
47
|
exports.reportTask = reportTask;
|
|
45
48
|
exports.reportCatalogueTask = reportCatalogueTask;
|
|
46
49
|
exports.uploadTask = uploadTask;
|
|
50
|
+
exports.claimSlugTask = claimSlugTask;
|
|
47
51
|
exports.completeTask = completeTask;
|
|
48
52
|
exports.readReviewEvidenceFiles = readReviewEvidenceFiles;
|
|
49
53
|
exports.submitReviewTask = submitReviewTask;
|
|
@@ -75,12 +79,16 @@ Object.defineProperty(exports, "buildClaudeExecArgs", { enumerable: true, get: f
|
|
|
75
79
|
Object.defineProperty(exports, "buildClaudePermissionSettings", { enumerable: true, get: function () { return runtime_2.buildClaudePermissionSettings; } });
|
|
76
80
|
Object.defineProperty(exports, "buildWorkerChildEnv", { enumerable: true, get: function () { return runtime_2.buildWorkerChildEnv; } });
|
|
77
81
|
Object.defineProperty(exports, "extractAgentTokenUsage", { enumerable: true, get: function () { return runtime_2.extractAgentTokenUsage; } });
|
|
82
|
+
Object.defineProperty(exports, "extractCodexRolloutTokenUsage", { enumerable: true, get: function () { return runtime_2.extractCodexRolloutTokenUsage; } });
|
|
83
|
+
Object.defineProperty(exports, "extractCodexThreadId", { enumerable: true, get: function () { return runtime_2.extractCodexThreadId; } });
|
|
78
84
|
Object.defineProperty(exports, "extractCodexTokensUsed", { enumerable: true, get: function () { return runtime_2.extractCodexTokensUsed; } });
|
|
79
85
|
Object.defineProperty(exports, "readEnvBoolean", { enumerable: true, get: function () { return runtime_2.readEnvBoolean; } });
|
|
80
86
|
Object.defineProperty(exports, "readCodexSandboxMode", { enumerable: true, get: function () { return runtime_2.readCodexSandboxMode; } });
|
|
87
|
+
Object.defineProperty(exports, "readCodexRolloutTokenUsageForThread", { enumerable: true, get: function () { return runtime_2.readCodexRolloutTokenUsageForThread; } });
|
|
81
88
|
Object.defineProperty(exports, "runLoggedProcess", { enumerable: true, get: function () { return runtime_2.runLoggedProcess; } });
|
|
82
89
|
const DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
83
90
|
const HEARTBEAT_INTERVAL_MS = 10000;
|
|
91
|
+
const TASK_HEARTBEAT_TRANSIENT_FAILURE_LIMIT = 6;
|
|
84
92
|
const DEFAULT_WORKER_MAX_PARALLEL_TASKS = 5;
|
|
85
93
|
const DEFAULT_WORKER_DEV_PORT_BASE = 8900;
|
|
86
94
|
const CLAIM_BACKOFF_BASE_MS = 1000;
|
|
@@ -92,67 +100,13 @@ const SLACK_API_BASE_URL = 'https://slack.com/api';
|
|
|
92
100
|
const WORKER_WRAPPER_CONTRACT_VERSION = 1;
|
|
93
101
|
const WORKER_SUPPORTED_KINDS = ['NEW_GAME', 'REMIX_GAME', 'GAME_UPDATE', 'GAME_REVIEW'];
|
|
94
102
|
const requireFromWorker = (0, node_module_1.createRequire)(__filename);
|
|
95
|
-
const PLAYDROP_PLUGIN_DAEMON_GAME_CREATION_SKILL_PATH = 'skills/daemon-game-creation/SKILL.md';
|
|
96
|
-
const PLAYDROP_PLUGIN_GAME_IMPROVEMENT_SKILL_PATH = 'skills/game-improvement/SKILL.md';
|
|
97
|
-
const PLAYDROP_PLUGIN_ASSET_DISCOVERY_SKILL_PATH = 'skills/asset-discovery/SKILL.md';
|
|
98
|
-
const PLAYDROP_PLUGIN_ASSET_EXTRACTION_SKILL_PATH = 'skills/asset-extraction-2d/SKILL.md';
|
|
99
|
-
const PLAYDROP_PLUGIN_LISTING_ART_SKILL_PATH = 'skills/listing-art/SKILL.md';
|
|
100
|
-
const PLAYDROP_PLUGIN_GAME_REVIEW_SKILL_PATH = 'skills/game-review/SKILL.md';
|
|
101
|
-
const PLAYDROP_PLUGIN_ASSET_REUSE_REFERENCE_PATH = 'references/asset-reuse.md';
|
|
102
|
-
const PLAYDROP_PLUGIN_ASSETS_AND_GENERATION_REFERENCE_PATH = 'references/assets-and-generation.md';
|
|
103
|
-
const PLAYDROP_PLUGIN_CODE_REUSE_REFERENCE_PATH = 'references/code-reuse.md';
|
|
104
|
-
const PLAYDROP_PLUGIN_EXTRACTOR_PATH = 'scripts/extract-alpha-background-swap.ts';
|
|
105
|
-
const PLAYDROP_PLUGIN_LISTING_ICON_SCRIPT_PATH = 'scripts/compose-listing-icon.ts';
|
|
106
|
-
const PLAYDROP_PLUGIN_LISTING_TITLE_SCRIPT_PATH = 'scripts/compose-listing-title.ts';
|
|
107
103
|
const STAGED_PLAYDROP_PLUGIN_ROOT = '.playdrop/plugin';
|
|
108
|
-
const
|
|
109
|
-
PLAYDROP_PLUGIN_DAEMON_GAME_CREATION_SKILL_PATH,
|
|
110
|
-
PLAYDROP_PLUGIN_GAME_IMPROVEMENT_SKILL_PATH,
|
|
111
|
-
PLAYDROP_PLUGIN_ASSET_DISCOVERY_SKILL_PATH,
|
|
112
|
-
PLAYDROP_PLUGIN_ASSET_EXTRACTION_SKILL_PATH,
|
|
113
|
-
PLAYDROP_PLUGIN_LISTING_ART_SKILL_PATH,
|
|
114
|
-
PLAYDROP_PLUGIN_ASSET_REUSE_REFERENCE_PATH,
|
|
115
|
-
PLAYDROP_PLUGIN_ASSETS_AND_GENERATION_REFERENCE_PATH,
|
|
116
|
-
PLAYDROP_PLUGIN_CODE_REUSE_REFERENCE_PATH,
|
|
117
|
-
PLAYDROP_PLUGIN_EXTRACTOR_PATH,
|
|
118
|
-
PLAYDROP_PLUGIN_LISTING_ICON_SCRIPT_PATH,
|
|
119
|
-
PLAYDROP_PLUGIN_LISTING_TITLE_SCRIPT_PATH,
|
|
120
|
-
];
|
|
121
|
-
const PLAYDROP_PLUGIN_GAME_REVIEW_REFERENCE_FILES = [
|
|
122
|
-
PLAYDROP_PLUGIN_GAME_REVIEW_SKILL_PATH,
|
|
123
|
-
'references/game-review-criteria.md',
|
|
124
|
-
'references/game-review-comparative-method.md',
|
|
125
|
-
'references/game-review-evidence-capture.md',
|
|
126
|
-
'references/game-review-rating-scale.md',
|
|
127
|
-
'references/game-review-score-caps.md',
|
|
128
|
-
'references/game-review-gates.md',
|
|
129
|
-
'references/game-review-gates/concept.md',
|
|
130
|
-
'references/game-review-gates/gameplay-prototype.md',
|
|
131
|
-
'references/game-review-gates/demo.md',
|
|
132
|
-
'references/game-review-gates/vertical-slice.md',
|
|
133
|
-
'references/game-review-gates/first-release.md',
|
|
134
|
-
'references/game-review-gates/mature-live-version.md',
|
|
135
|
-
'references/game-review-outcomes.md',
|
|
136
|
-
'references/game-review-feedback-format.md',
|
|
137
|
-
'references/game-review-dimensions/gameplay-core-loop.md',
|
|
138
|
-
'references/game-review-dimensions/depth-replayability.md',
|
|
139
|
-
'references/game-review-dimensions/controls-input.md',
|
|
140
|
-
'references/game-review-dimensions/ux-usability.md',
|
|
141
|
-
'references/game-review-dimensions/first-time-user-experience.md',
|
|
142
|
-
'references/game-review-dimensions/visuals-art-direction.md',
|
|
143
|
-
'references/game-review-dimensions/audio-feedback.md',
|
|
144
|
-
'references/game-review-dimensions/store-listing-metadata-accuracy.md',
|
|
145
|
-
'references/game-review-dimensions/safety-age-rating-compliance.md',
|
|
146
|
-
'references/game-review-dimensions/performance-stability.md',
|
|
147
|
-
];
|
|
148
|
-
const PLAYDROP_PLUGIN_REFERENCE_FILES = [
|
|
149
|
-
...PLAYDROP_PLUGIN_CREATION_REFERENCE_FILES,
|
|
150
|
-
...PLAYDROP_PLUGIN_GAME_REVIEW_REFERENCE_FILES,
|
|
151
|
-
];
|
|
104
|
+
const PLAYDROP_PLUGIN_STAGING_MANIFEST_PATH = 'playdrop-worker-staging.json';
|
|
152
105
|
exports.WORKER_SESSION_EXPIRED_MESSAGE = 'worker session expired: run "playdrop auth login" and start the worker again';
|
|
153
106
|
const WORKER_CONTEXT_ALLOWED_COMMANDS = [
|
|
154
107
|
['task', 'report'],
|
|
155
108
|
['task', 'report-catalogue'],
|
|
109
|
+
['task', 'claim-slug'],
|
|
156
110
|
['task', 'upload'],
|
|
157
111
|
['task', 'done'],
|
|
158
112
|
['task', 'submit-review'],
|
|
@@ -175,7 +129,7 @@ const WORKER_CONTEXT_ALLOWED_COMMANDS = [
|
|
|
175
129
|
['ai', 'jobs', 'detail'],
|
|
176
130
|
['ai', 'generations'],
|
|
177
131
|
];
|
|
178
|
-
exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = 'worker_context_command_not_allowed: inside a PlayDrop worker task only task progress, task upload/done/fail, assigned
|
|
132
|
+
exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = 'worker_context_command_not_allowed: inside a PlayDrop worker task only task progress, task claim-slug, task upload/done/fail, assigned app scaffolding, project validation/build/dev/capture, read-only catalogue/documentation lookup, and PlayDrop AI generation are permitted.';
|
|
179
133
|
function allocateWorkerDevPort(input) {
|
|
180
134
|
const basePort = input.basePort ?? DEFAULT_WORKER_DEV_PORT_BASE;
|
|
181
135
|
const maxParallelTasks = input.maxParallelTasks ?? DEFAULT_WORKER_MAX_PARALLEL_TASKS;
|
|
@@ -203,7 +157,7 @@ function isWorkerContextCommandAllowed(argv) {
|
|
|
203
157
|
if (WORKER_CONTEXT_ALLOWED_COMMANDS.some((allowed) => allowed.every((part, index) => argv[index] === part))) {
|
|
204
158
|
return true;
|
|
205
159
|
}
|
|
206
|
-
return
|
|
160
|
+
return isAssignedAppScaffoldCommandAllowed(argv);
|
|
207
161
|
}
|
|
208
162
|
function readWorkerContextForPolicy() {
|
|
209
163
|
try {
|
|
@@ -227,7 +181,7 @@ function normalizeRemixFlagValue(argv) {
|
|
|
227
181
|
}
|
|
228
182
|
return null;
|
|
229
183
|
}
|
|
230
|
-
function
|
|
184
|
+
function isAssignedAppScaffoldCommandAllowed(argv) {
|
|
231
185
|
if (argv[0] !== 'project' || argv[1] !== 'create' || argv[2] !== 'app') {
|
|
232
186
|
return false;
|
|
233
187
|
}
|
|
@@ -239,7 +193,12 @@ function isAssignedRemixScaffoldCommandAllowed(argv) {
|
|
|
239
193
|
if (context?.kind !== 'REMIX_GAME'
|
|
240
194
|
|| !context.outputAppName
|
|
241
195
|
|| requestedAppName !== context.outputAppName) {
|
|
242
|
-
|
|
196
|
+
if (context?.kind !== 'NEW_GAME'
|
|
197
|
+
|| !context.outputAppName
|
|
198
|
+
|| requestedAppName !== context.outputAppName) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
return normalizeRemixFlagValue(argv) === null;
|
|
243
202
|
}
|
|
244
203
|
return normalizeRemixFlagValue(argv) === context.remixSourceRef;
|
|
245
204
|
}
|
|
@@ -465,6 +424,9 @@ async function stageWorkerTaskContext(input) {
|
|
|
465
424
|
taskToken: input.taskContext.taskToken,
|
|
466
425
|
}, null, 2), 'utf8');
|
|
467
426
|
}
|
|
427
|
+
async function writeWorkerTaskContextFile(workspaceDir, taskContext) {
|
|
428
|
+
await (0, promises_1.writeFile)(workerTaskContextPath(workspaceDir), JSON.stringify(taskContext, null, 2), 'utf8');
|
|
429
|
+
}
|
|
468
430
|
function findWorkspaceTaskFile(startDir, basename) {
|
|
469
431
|
let current = node_path_1.default.resolve(startDir);
|
|
470
432
|
while (true) {
|
|
@@ -598,6 +560,122 @@ function readTaskUploadResultFile(startDir = node_process_1.default.cwd()) {
|
|
|
598
560
|
}
|
|
599
561
|
return { taskId, appId, appVersionId, appName, version, versionNodeId, creatorUsername };
|
|
600
562
|
}
|
|
563
|
+
const AGENT_TASK_NEXT_STEP_CATEGORY_SET = new Set(types_1.AGENT_TASK_NEXT_STEP_CATEGORIES);
|
|
564
|
+
const AGENT_TASK_CLAIM_SLUG_REGEX = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
|
|
565
|
+
const NEXT_STEPS_SCHEMA_HELP = [
|
|
566
|
+
'Expected next-steps.json format:',
|
|
567
|
+
'{"nextSteps":[{"id":"tune-difficulty","title":"Tune difficulty","description":"Make the opening easier and add a sharper ramp.","prompt":"Tune the game difficulty so the first minute is approachable and the second minute becomes harder.","category":"MECHANICS"}]}',
|
|
568
|
+
`Allowed categories: ${types_1.AGENT_TASK_NEXT_STEP_CATEGORIES.join(', ')}`,
|
|
569
|
+
];
|
|
570
|
+
const NEXT_STEPS_READ_FAILED = Symbol('NEXT_STEPS_READ_FAILED');
|
|
571
|
+
function normalizeTaskClaimSlugAppName(value) {
|
|
572
|
+
const appName = typeof value === 'string' ? value.trim() : '';
|
|
573
|
+
if (!appName || appName.length > 128 || !AGENT_TASK_CLAIM_SLUG_REGEX.test(appName)) {
|
|
574
|
+
throw new Error('invalid_slug_format');
|
|
575
|
+
}
|
|
576
|
+
return appName;
|
|
577
|
+
}
|
|
578
|
+
function normalizeTaskClaimSlugDisplayName(value) {
|
|
579
|
+
const displayName = typeof value === 'string' ? value.trim() : '';
|
|
580
|
+
if (!displayName || displayName.length > 120) {
|
|
581
|
+
throw new Error('invalid_display_name');
|
|
582
|
+
}
|
|
583
|
+
return displayName;
|
|
584
|
+
}
|
|
585
|
+
function readTaskNextStepsFile(filePath) {
|
|
586
|
+
const normalizedPath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
587
|
+
if (!normalizedPath) {
|
|
588
|
+
return undefined;
|
|
589
|
+
}
|
|
590
|
+
const resolved = node_path_1.default.resolve(normalizedPath);
|
|
591
|
+
if (!(0, node_fs_1.existsSync)(resolved)) {
|
|
592
|
+
throw new Error('invalid_agent_task_next_steps_file');
|
|
593
|
+
}
|
|
594
|
+
let parsed;
|
|
595
|
+
try {
|
|
596
|
+
parsed = JSON.parse((0, node_fs_1.readFileSync)(resolved, 'utf8'));
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
throw new Error('invalid_agent_task_next_steps_file');
|
|
600
|
+
}
|
|
601
|
+
let rawSteps = null;
|
|
602
|
+
if (Array.isArray(parsed)) {
|
|
603
|
+
rawSteps = parsed;
|
|
604
|
+
}
|
|
605
|
+
else if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
606
|
+
const object = parsed;
|
|
607
|
+
if (Array.isArray(object['nextSteps'])) {
|
|
608
|
+
rawSteps = object['nextSteps'];
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (!rawSteps) {
|
|
612
|
+
throw new Error('invalid_agent_task_next_steps');
|
|
613
|
+
}
|
|
614
|
+
if (rawSteps.length > 3) {
|
|
615
|
+
throw new Error('invalid_agent_task_next_steps_count');
|
|
616
|
+
}
|
|
617
|
+
const ids = new Set();
|
|
618
|
+
return rawSteps.map((entry) => {
|
|
619
|
+
const object = entry && typeof entry === 'object' && !Array.isArray(entry)
|
|
620
|
+
? entry
|
|
621
|
+
: null;
|
|
622
|
+
if (!object) {
|
|
623
|
+
throw new Error('invalid_agent_task_next_step');
|
|
624
|
+
}
|
|
625
|
+
const id = typeof object['id'] === 'string' ? object['id'].trim() : '';
|
|
626
|
+
if (!id || id.length > 64 || !types_1.AGENT_TASK_NEXT_STEP_ID_REGEX.test(id)) {
|
|
627
|
+
throw new Error('invalid_agent_task_next_step_id');
|
|
628
|
+
}
|
|
629
|
+
if (ids.has(id)) {
|
|
630
|
+
throw new Error('invalid_agent_task_next_step_id_duplicate');
|
|
631
|
+
}
|
|
632
|
+
ids.add(id);
|
|
633
|
+
const title = typeof object['title'] === 'string' ? object['title'].trim() : '';
|
|
634
|
+
if (!title || title.length > 120) {
|
|
635
|
+
throw new Error('invalid_agent_task_next_step_title');
|
|
636
|
+
}
|
|
637
|
+
const description = typeof object['description'] === 'string' ? object['description'].trim() : '';
|
|
638
|
+
if (!description || description.length > 400) {
|
|
639
|
+
throw new Error('invalid_agent_task_next_step_description');
|
|
640
|
+
}
|
|
641
|
+
const prompt = typeof object['prompt'] === 'string' ? object['prompt'].trim() : '';
|
|
642
|
+
if (!prompt || prompt.length > 1200) {
|
|
643
|
+
throw new Error('invalid_agent_task_next_step_prompt');
|
|
644
|
+
}
|
|
645
|
+
const category = typeof object['category'] === 'string' ? object['category'].trim() : '';
|
|
646
|
+
if (!AGENT_TASK_NEXT_STEP_CATEGORY_SET.has(category)) {
|
|
647
|
+
throw new Error('invalid_agent_task_next_step_category');
|
|
648
|
+
}
|
|
649
|
+
return { id, title, description, prompt, category: category };
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
async function recordRejectedNextStepsFile(input) {
|
|
653
|
+
if (!input.ctx) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
await input.ctx.client.workerCreateAgentTaskEvent(input.taskContext.taskId, {
|
|
657
|
+
taskToken: input.taskContext.taskToken,
|
|
658
|
+
kind: 'progress',
|
|
659
|
+
phase: 'next-steps',
|
|
660
|
+
message: `Rejected next-steps.json: ${input.code}`,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
async function readTaskNextStepsFileForCommand(input) {
|
|
664
|
+
try {
|
|
665
|
+
return readTaskNextStepsFile(input.filePath);
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
const code = error instanceof Error ? error.message : String(error);
|
|
669
|
+
await recordRejectedNextStepsFile({
|
|
670
|
+
ctx: input.ctx,
|
|
671
|
+
taskContext: input.taskContext,
|
|
672
|
+
code,
|
|
673
|
+
});
|
|
674
|
+
(0, messages_1.printErrorWithHelp)(code, NEXT_STEPS_SCHEMA_HELP, { command: input.command });
|
|
675
|
+
node_process_1.default.exitCode = 1;
|
|
676
|
+
return NEXT_STEPS_READ_FAILED;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
601
679
|
function assertTaskUploadResultMatchesContext(input) {
|
|
602
680
|
if (input.uploadResult.taskId !== input.taskContext.taskId) {
|
|
603
681
|
throw new Error('task_upload_result_task_mismatch');
|
|
@@ -693,8 +771,97 @@ function candidateVersionDirectories(root) {
|
|
|
693
771
|
.sort((left, right) => right.localeCompare(left, undefined, { numeric: true }))
|
|
694
772
|
.map((name) => node_path_1.default.join(root, name));
|
|
695
773
|
}
|
|
696
|
-
function
|
|
697
|
-
|
|
774
|
+
function isDirectory(candidatePath) {
|
|
775
|
+
try {
|
|
776
|
+
return (0, node_fs_1.statSync)(candidatePath).isDirectory();
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function isFile(candidatePath) {
|
|
783
|
+
try {
|
|
784
|
+
return (0, node_fs_1.statSync)(candidatePath).isFile();
|
|
785
|
+
}
|
|
786
|
+
catch {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function looksLikePlaydropPluginRoot(pluginRoot) {
|
|
791
|
+
return isDirectory(pluginRoot) && ((0, node_fs_1.existsSync)(node_path_1.default.join(pluginRoot, PLAYDROP_PLUGIN_STAGING_MANIFEST_PATH))
|
|
792
|
+
|| isDirectory(node_path_1.default.join(pluginRoot, 'skills'))
|
|
793
|
+
|| isFile(node_path_1.default.join(pluginRoot, '.codex-plugin', 'plugin.json'))
|
|
794
|
+
|| isFile(node_path_1.default.join(pluginRoot, '.claude-plugin', 'plugin.json'))
|
|
795
|
+
|| isFile(node_path_1.default.join(pluginRoot, '.cursor-plugin', 'plugin.json')));
|
|
796
|
+
}
|
|
797
|
+
function normalizePlaydropPluginManifestPath(input) {
|
|
798
|
+
if (typeof input.value !== 'string' || input.value.trim().length <= 0) {
|
|
799
|
+
throw new Error(`playdrop_plugin_staging_manifest_incomplete:${input.kind}: file paths must be non-empty strings in ${input.manifestPath}`);
|
|
800
|
+
}
|
|
801
|
+
if (input.value.includes('\0') || input.value.includes('\\')) {
|
|
802
|
+
throw new Error(`playdrop_plugin_staging_manifest_invalid_path:${input.kind}:${input.value}`);
|
|
803
|
+
}
|
|
804
|
+
if (node_path_1.default.isAbsolute(input.value) || /^[A-Za-z]:[\\/]/.test(input.value)) {
|
|
805
|
+
throw new Error(`playdrop_plugin_staging_manifest_invalid_path:${input.kind}:${input.value}`);
|
|
806
|
+
}
|
|
807
|
+
const normalized = node_path_1.default.posix.normalize(input.value);
|
|
808
|
+
if (normalized === '.' || normalized === '..' || normalized.startsWith('../')) {
|
|
809
|
+
throw new Error(`playdrop_plugin_staging_manifest_invalid_path:${input.kind}:${input.value}`);
|
|
810
|
+
}
|
|
811
|
+
return normalized;
|
|
812
|
+
}
|
|
813
|
+
function readPlaydropPluginStagingManifest(pluginRoot) {
|
|
814
|
+
const resolvedRoot = node_path_1.default.resolve(pluginRoot);
|
|
815
|
+
const manifestPath = node_path_1.default.join(resolvedRoot, PLAYDROP_PLUGIN_STAGING_MANIFEST_PATH);
|
|
816
|
+
if (!isFile(manifestPath)) {
|
|
817
|
+
throw new Error(`playdrop_plugin_staging_manifest_missing: expected ${PLAYDROP_PLUGIN_STAGING_MANIFEST_PATH} under ${resolvedRoot}`);
|
|
818
|
+
}
|
|
819
|
+
let parsed;
|
|
820
|
+
try {
|
|
821
|
+
parsed = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, 'utf8'));
|
|
822
|
+
}
|
|
823
|
+
catch (error) {
|
|
824
|
+
throw new Error(`playdrop_plugin_staging_manifest_invalid_json:${manifestPath}:${error instanceof Error ? error.message : String(error)}`);
|
|
825
|
+
}
|
|
826
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
827
|
+
throw new Error(`playdrop_plugin_staging_manifest_invalid:${manifestPath}`);
|
|
828
|
+
}
|
|
829
|
+
const rawManifest = parsed;
|
|
830
|
+
if (rawManifest.version !== 1) {
|
|
831
|
+
throw new Error(`playdrop_plugin_staging_manifest_unsupported_version:${manifestPath}`);
|
|
832
|
+
}
|
|
833
|
+
if (!rawManifest.taskFiles || typeof rawManifest.taskFiles !== 'object' || Array.isArray(rawManifest.taskFiles)) {
|
|
834
|
+
throw new Error(`playdrop_plugin_staging_manifest_incomplete: taskFiles missing in ${manifestPath}`);
|
|
835
|
+
}
|
|
836
|
+
const taskFiles = {};
|
|
837
|
+
const rawTaskFiles = rawManifest.taskFiles;
|
|
838
|
+
for (const kind of WORKER_SUPPORTED_KINDS) {
|
|
839
|
+
const rawFiles = rawTaskFiles[kind];
|
|
840
|
+
if (!Array.isArray(rawFiles) || rawFiles.length <= 0) {
|
|
841
|
+
throw new Error(`playdrop_plugin_staging_manifest_incomplete:${kind}: non-empty taskFiles entry required in ${manifestPath}`);
|
|
842
|
+
}
|
|
843
|
+
const seen = new Set();
|
|
844
|
+
const files = rawFiles.map((value) => normalizePlaydropPluginManifestPath({ manifestPath, kind, value }));
|
|
845
|
+
for (const relativePath of files) {
|
|
846
|
+
if (seen.has(relativePath)) {
|
|
847
|
+
throw new Error(`playdrop_plugin_staging_manifest_duplicate_file:${kind}:${relativePath}`);
|
|
848
|
+
}
|
|
849
|
+
seen.add(relativePath);
|
|
850
|
+
const source = node_path_1.default.resolve(resolvedRoot, ...relativePath.split('/'));
|
|
851
|
+
const relativeToRoot = node_path_1.default.relative(resolvedRoot, source);
|
|
852
|
+
if (relativeToRoot.startsWith('..') || node_path_1.default.isAbsolute(relativeToRoot)) {
|
|
853
|
+
throw new Error(`playdrop_plugin_staging_manifest_invalid_path:${kind}:${relativePath}`);
|
|
854
|
+
}
|
|
855
|
+
if (!isFile(source)) {
|
|
856
|
+
throw new Error(`playdrop_plugin_staging_manifest_file_missing:${kind}:${relativePath}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
taskFiles[kind] = files;
|
|
860
|
+
}
|
|
861
|
+
return {
|
|
862
|
+
version: 1,
|
|
863
|
+
taskFiles,
|
|
864
|
+
};
|
|
698
865
|
}
|
|
699
866
|
function resolvePlaydropPluginRoot(input = {}) {
|
|
700
867
|
const homeDir = input.homeDir ?? node_os_1.default.homedir();
|
|
@@ -702,9 +869,7 @@ function resolvePlaydropPluginRoot(input = {}) {
|
|
|
702
869
|
const explicitRoot = env.PLAYDROP_WORKER_PLAYDROP_PLUGIN_DIR?.trim();
|
|
703
870
|
if (explicitRoot) {
|
|
704
871
|
const resolved = node_path_1.default.resolve(explicitRoot);
|
|
705
|
-
|
|
706
|
-
throw new Error(`playdrop_plugin_reference_missing: expected PlayDrop plugin skills and scripts under ${resolved}`);
|
|
707
|
-
}
|
|
872
|
+
readPlaydropPluginStagingManifest(resolved);
|
|
708
873
|
return resolved;
|
|
709
874
|
}
|
|
710
875
|
const candidates = [
|
|
@@ -716,27 +881,22 @@ function resolvePlaydropPluginRoot(input = {}) {
|
|
|
716
881
|
...candidateVersionDirectories(node_path_1.default.join(homeDir, '.codex', 'plugins', 'cache', 'playdrop', 'playdrop')),
|
|
717
882
|
...candidateVersionDirectories(node_path_1.default.join(homeDir, '.codex', 'plugins', 'cache', 'olivier-local-plugins', 'playdrop')),
|
|
718
883
|
];
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
884
|
+
for (const candidate of candidates) {
|
|
885
|
+
if (!looksLikePlaydropPluginRoot(candidate)) {
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
readPlaydropPluginStagingManifest(candidate);
|
|
889
|
+
return candidate;
|
|
722
890
|
}
|
|
723
|
-
|
|
891
|
+
throw new Error(`playdrop_plugin_staging_manifest_missing: install or update the PlayDrop plugin so ${PLAYDROP_PLUGIN_STAGING_MANIFEST_PATH} is available.`);
|
|
724
892
|
}
|
|
725
893
|
async function stagePlaydropPluginReferences(input) {
|
|
726
894
|
const pluginRoot = node_path_1.default.resolve(input.pluginRoot);
|
|
727
|
-
if (!
|
|
728
|
-
throw new Error(`playdrop_plugin_reference_missing: expected required PlayDrop plugin skills and scripts under ${pluginRoot}`);
|
|
729
|
-
}
|
|
730
|
-
let referenceFiles;
|
|
731
|
-
if (input.kind === 'GAME_REVIEW') {
|
|
732
|
-
referenceFiles = PLAYDROP_PLUGIN_GAME_REVIEW_REFERENCE_FILES;
|
|
733
|
-
}
|
|
734
|
-
else if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME' || input.kind === 'GAME_UPDATE') {
|
|
735
|
-
referenceFiles = PLAYDROP_PLUGIN_CREATION_REFERENCE_FILES;
|
|
736
|
-
}
|
|
737
|
-
else {
|
|
895
|
+
if (!WORKER_SUPPORTED_KINDS.includes(input.kind)) {
|
|
738
896
|
throw new Error(`unsupported_agent_task_kind:${input.kind}`);
|
|
739
897
|
}
|
|
898
|
+
const manifest = readPlaydropPluginStagingManifest(pluginRoot);
|
|
899
|
+
const referenceFiles = manifest.taskFiles[input.kind];
|
|
740
900
|
const staged = [];
|
|
741
901
|
for (const relativePath of referenceFiles) {
|
|
742
902
|
const destinationRelativePath = node_path_1.default.join(STAGED_PLAYDROP_PLUGIN_ROOT, relativePath);
|
|
@@ -930,6 +1090,38 @@ function classifyWorkerEventDrainError(error) {
|
|
|
930
1090
|
}
|
|
931
1091
|
return null;
|
|
932
1092
|
}
|
|
1093
|
+
function readErrorCauseCode(error) {
|
|
1094
|
+
const cause = error instanceof Error ? error.cause : null;
|
|
1095
|
+
if (cause && typeof cause === 'object' && 'code' in cause && typeof cause.code === 'string') {
|
|
1096
|
+
return cause.code;
|
|
1097
|
+
}
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
function isWorkerTransientNetworkError(error) {
|
|
1101
|
+
if (!(error instanceof Error)) {
|
|
1102
|
+
return false;
|
|
1103
|
+
}
|
|
1104
|
+
const message = error.message.toLowerCase();
|
|
1105
|
+
if (message.includes('fetch failed') || message.includes('socket hang up')) {
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
const causeCode = readErrorCauseCode(error);
|
|
1109
|
+
return causeCode
|
|
1110
|
+
? ['ECONNRESET', 'ECONNREFUSED', 'EPIPE', 'ETIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT'].includes(causeCode)
|
|
1111
|
+
: false;
|
|
1112
|
+
}
|
|
1113
|
+
function classifyWorkerHeartbeatError(error) {
|
|
1114
|
+
if (isWorkerAuthFailureError(error)) {
|
|
1115
|
+
return 'session_expired';
|
|
1116
|
+
}
|
|
1117
|
+
if (isLeaseInvalidError(error)) {
|
|
1118
|
+
return 'lease_invalid';
|
|
1119
|
+
}
|
|
1120
|
+
if (isWorkerTransientNetworkError(error)) {
|
|
1121
|
+
return 'transient_network';
|
|
1122
|
+
}
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
933
1125
|
// Throws for claim errors that must stop the worker; returns for transient
|
|
934
1126
|
// errors the claim loop may retry with backoff.
|
|
935
1127
|
function assertWorkerClaimErrorRetryable(error) {
|
|
@@ -1349,10 +1541,15 @@ function normalizeQueuedWorkerEvent(value) {
|
|
|
1349
1541
|
if (!kind) {
|
|
1350
1542
|
throw new Error('agent_task_worker_event_kind_invalid');
|
|
1351
1543
|
}
|
|
1352
|
-
const message = typeof object.message === 'string'
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1544
|
+
const message = typeof object.message === 'string' && object.message.trim()
|
|
1545
|
+
? object.message.trim()
|
|
1546
|
+
: null;
|
|
1547
|
+
const done = typeof object.done === 'string' && object.done.trim()
|
|
1548
|
+
? object.done.trim()
|
|
1549
|
+
: null;
|
|
1550
|
+
const current = typeof object.current === 'string' && object.current.trim()
|
|
1551
|
+
? object.current.trim()
|
|
1552
|
+
: null;
|
|
1356
1553
|
const phase = typeof object.phase === 'string' && object.phase.trim()
|
|
1357
1554
|
? object.phase.trim()
|
|
1358
1555
|
: null;
|
|
@@ -1365,10 +1562,16 @@ function normalizeQueuedWorkerEvent(value) {
|
|
|
1365
1562
|
if (kind === 'catalogue_preview' && !payload) {
|
|
1366
1563
|
throw new Error('agent_task_worker_event_payload_missing');
|
|
1367
1564
|
}
|
|
1565
|
+
if (kind === 'catalogue_preview' && !message) {
|
|
1566
|
+
throw new Error('agent_task_worker_event_message_missing');
|
|
1567
|
+
}
|
|
1568
|
+
if (kind === 'progress' && !message && !done && !current) {
|
|
1569
|
+
throw new Error('agent_task_worker_event_message_missing');
|
|
1570
|
+
}
|
|
1368
1571
|
if (kind === 'progress') {
|
|
1369
|
-
return { kind, phase, message, pct };
|
|
1572
|
+
return { kind, phase, message, done, current, pct };
|
|
1370
1573
|
}
|
|
1371
|
-
return { kind, phase, message, pct, payload };
|
|
1574
|
+
return { kind, phase, message, done, current, pct, payload };
|
|
1372
1575
|
}
|
|
1373
1576
|
async function drainWorkerEventQueue(input) {
|
|
1374
1577
|
if (!(0, node_fs_1.existsSync)(input.eventDir)) {
|
|
@@ -1386,8 +1589,10 @@ async function drainWorkerEventQueue(input) {
|
|
|
1386
1589
|
leaseToken: input.leaseToken,
|
|
1387
1590
|
kind: event.kind,
|
|
1388
1591
|
phase: event.phase,
|
|
1389
|
-
message: event.message,
|
|
1390
|
-
|
|
1592
|
+
...(event.message ? { message: event.message } : {}),
|
|
1593
|
+
...(event.done ? { done: event.done } : {}),
|
|
1594
|
+
...(event.current ? { current: event.current } : {}),
|
|
1595
|
+
...(event.pct !== null && event.pct !== undefined ? { pct: event.pct } : {}),
|
|
1391
1596
|
...(event.payload ? { payload: event.payload } : {}),
|
|
1392
1597
|
});
|
|
1393
1598
|
(0, node_fs_1.rmSync)(filePath, { force: true });
|
|
@@ -1443,7 +1648,9 @@ function resolveClaudeRuntimeModel(model) {
|
|
|
1443
1648
|
return model;
|
|
1444
1649
|
}
|
|
1445
1650
|
async function runCodex(input) {
|
|
1446
|
-
|
|
1651
|
+
let observedCodexThreadId = null;
|
|
1652
|
+
let codexThreadProbe = '';
|
|
1653
|
+
const result = await (0, runtime_1.runLoggedProcess)({
|
|
1447
1654
|
command: 'codex',
|
|
1448
1655
|
args: (0, runtime_1.buildCodexExecArgs)({
|
|
1449
1656
|
workspaceDir: input.workspaceDir,
|
|
@@ -1464,10 +1671,41 @@ async function runCodex(input) {
|
|
|
1464
1671
|
stdin: input.prompt,
|
|
1465
1672
|
timeoutMs: (0, runtime_1.readPositiveEnvInt)('PLAYDROP_WORKER_CODEX_TIMEOUT_MS', runtime_1.DEFAULT_CODEX_TIMEOUT_MS),
|
|
1466
1673
|
maxOutputChars: runtime_1.DEFAULT_CODEX_LOG_TAIL_CHARS,
|
|
1467
|
-
onTranscriptChunks:
|
|
1674
|
+
onTranscriptChunks: async (chunks) => {
|
|
1675
|
+
if (!observedCodexThreadId) {
|
|
1676
|
+
codexThreadProbe = (codexThreadProbe + chunks.map((chunk) => chunk.content).join('')).slice(-64000);
|
|
1677
|
+
observedCodexThreadId = (0, runtime_1.extractCodexThreadId)(codexThreadProbe);
|
|
1678
|
+
}
|
|
1679
|
+
await input.onTranscriptChunks(chunks);
|
|
1680
|
+
},
|
|
1468
1681
|
transcriptFlushIntervalMs: runtime_1.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS,
|
|
1469
1682
|
onChild: input.onChild,
|
|
1470
1683
|
});
|
|
1684
|
+
observedCodexThreadId = observedCodexThreadId
|
|
1685
|
+
?? (0, runtime_1.extractCodexThreadId)(`${result.stdout}\n${result.stderr}\n${result.outputTail}`);
|
|
1686
|
+
if (!observedCodexThreadId) {
|
|
1687
|
+
if (result.tokenUsage.usageParseError) {
|
|
1688
|
+
console.warn('Codex thread id was not observed; rollout token usage could not be read.');
|
|
1689
|
+
}
|
|
1690
|
+
return result;
|
|
1691
|
+
}
|
|
1692
|
+
const rolloutUsage = await (0, runtime_1.readCodexRolloutTokenUsageForThread)(observedCodexThreadId);
|
|
1693
|
+
if (rolloutUsage.usageParseError || rolloutUsage.totalTokens === null) {
|
|
1694
|
+
if (result.tokenUsage.usageParseError) {
|
|
1695
|
+
console.warn(`Codex rollout token usage could not be read for thread ${observedCodexThreadId}: ${rolloutUsage.usageParseError ?? 'unknown_error'}.`);
|
|
1696
|
+
}
|
|
1697
|
+
return result;
|
|
1698
|
+
}
|
|
1699
|
+
if (!result.tokenUsage.usageParseError
|
|
1700
|
+
&& result.tokenUsage.totalTokens !== null
|
|
1701
|
+
&& result.tokenUsage.totalTokens !== rolloutUsage.totalTokens) {
|
|
1702
|
+
console.warn(`Codex stream token usage (${result.tokenUsage.totalTokens}) differed from rollout token usage (${rolloutUsage.totalTokens}); using rollout usage.`);
|
|
1703
|
+
}
|
|
1704
|
+
return {
|
|
1705
|
+
...result,
|
|
1706
|
+
tokenUsage: rolloutUsage,
|
|
1707
|
+
tokensUsed: rolloutUsage.totalTokens,
|
|
1708
|
+
};
|
|
1471
1709
|
}
|
|
1472
1710
|
async function runClaude(input) {
|
|
1473
1711
|
const denyReadRoots = resolveClaudeDenyReadRoots(input.workspaceDir);
|
|
@@ -1781,6 +2019,26 @@ function readPlaydropPluginVersion(pluginRoot) {
|
|
|
1781
2019
|
}
|
|
1782
2020
|
return null;
|
|
1783
2021
|
}
|
|
2022
|
+
function readWorkerTargetUpdateFailure() {
|
|
2023
|
+
const failureFile = node_process_1.default.env.PLAYDROP_WORKER_TARGET_UPDATE_FAILURE_FILE?.trim() ?? '';
|
|
2024
|
+
if (!failureFile || !(0, node_fs_1.existsSync)(failureFile)) {
|
|
2025
|
+
return null;
|
|
2026
|
+
}
|
|
2027
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(failureFile, 'utf8'));
|
|
2028
|
+
const error = typeof parsed.error === 'string' ? parsed.error.trim() : '';
|
|
2029
|
+
const writtenAt = typeof parsed.writtenAt === 'string' ? parsed.writtenAt.trim() : '';
|
|
2030
|
+
if (!error || !writtenAt) {
|
|
2031
|
+
throw new Error('worker_update_target_failure_marker_invalid');
|
|
2032
|
+
}
|
|
2033
|
+
return {
|
|
2034
|
+
error,
|
|
2035
|
+
writtenAt,
|
|
2036
|
+
targetCliVersion: typeof parsed.targetCliVersion === 'string' ? parsed.targetCliVersion : null,
|
|
2037
|
+
minimumCliVersion: typeof parsed.minimumCliVersion === 'string' ? parsed.minimumCliVersion : null,
|
|
2038
|
+
targetPluginVersion: typeof parsed.targetPluginVersion === 'string' ? parsed.targetPluginVersion : null,
|
|
2039
|
+
minimumPluginVersion: typeof parsed.minimumPluginVersion === 'string' ? parsed.minimumPluginVersion : null,
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
1784
2042
|
function collectWorkerLocalStatus() {
|
|
1785
2043
|
const degradedReasons = [];
|
|
1786
2044
|
let codexVersion = null;
|
|
@@ -1791,6 +2049,7 @@ function collectWorkerLocalStatus() {
|
|
|
1791
2049
|
let pluginRoot = null;
|
|
1792
2050
|
let pluginVersion = null;
|
|
1793
2051
|
let pluginError = null;
|
|
2052
|
+
let targetUpdateFailure = null;
|
|
1794
2053
|
try {
|
|
1795
2054
|
codexVersion = probeCodexInstallation()?.codexVersion ?? null;
|
|
1796
2055
|
}
|
|
@@ -1829,6 +2088,12 @@ function collectWorkerLocalStatus() {
|
|
|
1829
2088
|
pluginError = errorMessage(error);
|
|
1830
2089
|
degradedReasons.push(errorCode(error));
|
|
1831
2090
|
}
|
|
2091
|
+
try {
|
|
2092
|
+
targetUpdateFailure = readWorkerTargetUpdateFailure();
|
|
2093
|
+
}
|
|
2094
|
+
catch (error) {
|
|
2095
|
+
degradedReasons.push(errorCode(error));
|
|
2096
|
+
}
|
|
1832
2097
|
let capabilities = null;
|
|
1833
2098
|
try {
|
|
1834
2099
|
capabilities = buildWorkerCapabilities({
|
|
@@ -1849,6 +2114,9 @@ function collectWorkerLocalStatus() {
|
|
|
1849
2114
|
agent.pluginVersion = pluginVersion;
|
|
1850
2115
|
}
|
|
1851
2116
|
}
|
|
2117
|
+
if (targetUpdateFailure) {
|
|
2118
|
+
capabilities.targetUpdateFailure = targetUpdateFailure;
|
|
2119
|
+
}
|
|
1852
2120
|
for (const reason of capabilities.degradedReasons ?? []) {
|
|
1853
2121
|
degradedReasons.push(reason);
|
|
1854
2122
|
}
|
|
@@ -2103,23 +2371,42 @@ set -eu
|
|
|
2103
2371
|
export PLAYDROP_WORKER_WRAPPER_CONTRACT_VERSION="${WORKER_WRAPPER_CONTRACT_VERSION}"
|
|
2104
2372
|
POLICY_FILE="${policyFile}"
|
|
2105
2373
|
FAILURE_FILE="${failureFile}"
|
|
2374
|
+
export PLAYDROP_WORKER_TARGET_UPDATE_FAILURE_FILE="$FAILURE_FILE"
|
|
2106
2375
|
BACKOFF_SECONDS="\${PLAYDROP_WORKER_UPDATE_FAILURE_BACKOFF_SECONDS:-30}"
|
|
2376
|
+
TARGET_CLI_VERSION=""
|
|
2377
|
+
MINIMUM_CLI_VERSION=""
|
|
2378
|
+
TARGET_PLUGIN_VERSION=""
|
|
2379
|
+
MINIMUM_PLUGIN_VERSION=""
|
|
2380
|
+
TARGET_UPDATE_FAILED=0
|
|
2107
2381
|
|
|
2108
2382
|
record_failure() {
|
|
2109
|
-
node - "$FAILURE_FILE" "$1" <<'NODE'
|
|
2383
|
+
node - "$FAILURE_FILE" "$1" "$TARGET_CLI_VERSION" "$MINIMUM_CLI_VERSION" "$TARGET_PLUGIN_VERSION" "$MINIMUM_PLUGIN_VERSION" <<'NODE'
|
|
2110
2384
|
const fs = require("fs");
|
|
2111
2385
|
const path = require("path");
|
|
2112
2386
|
const file = process.argv[2];
|
|
2113
2387
|
const error = process.argv[3] || "worker_update_install_failed";
|
|
2388
|
+
const targetCliVersion = process.argv[4] || null;
|
|
2389
|
+
const minimumCliVersion = process.argv[5] || null;
|
|
2390
|
+
const targetPluginVersion = process.argv[6] || null;
|
|
2391
|
+
const minimumPluginVersion = process.argv[7] || null;
|
|
2114
2392
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
2115
2393
|
fs.writeFileSync(file, JSON.stringify({
|
|
2116
2394
|
schemaVersion: 1,
|
|
2117
2395
|
writtenAt: new Date().toISOString(),
|
|
2118
2396
|
error,
|
|
2397
|
+
targetCliVersion,
|
|
2398
|
+
minimumCliVersion,
|
|
2399
|
+
targetPluginVersion,
|
|
2400
|
+
minimumPluginVersion,
|
|
2119
2401
|
}, null, 2));
|
|
2120
2402
|
NODE
|
|
2121
2403
|
}
|
|
2122
2404
|
|
|
2405
|
+
record_target_failure() {
|
|
2406
|
+
TARGET_UPDATE_FAILED=1
|
|
2407
|
+
record_failure "worker_update_target_failed:$1"
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2123
2410
|
fail_converge() {
|
|
2124
2411
|
code="$1"
|
|
2125
2412
|
reason="$2"
|
|
@@ -2156,24 +2443,69 @@ read_policy_value() {
|
|
|
2156
2443
|
node -e 'const fs = require("fs"); const file = process.argv[1]; const key = process.argv[2]; if (!fs.existsSync(file)) process.exit(0); const policy = JSON.parse(fs.readFileSync(file, "utf8")); const value = policy && policy[key]; if (typeof value === "string") process.stdout.write(value);' "$POLICY_FILE" "$1"
|
|
2157
2444
|
}
|
|
2158
2445
|
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2446
|
+
current_cli_version() {
|
|
2447
|
+
playdrop --version 2>/dev/null | node -e 'const fs = require("fs"); const input = fs.readFileSync(0, "utf8"); const match = input.match(/\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)?/); if (!match) process.exit(1); process.stdout.write(match[0]);'
|
|
2448
|
+
}
|
|
2162
2449
|
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2450
|
+
version_meets() {
|
|
2451
|
+
node - "$1" "$2" <<'NODE'
|
|
2452
|
+
const current = String(process.argv[2] || "").trim();
|
|
2453
|
+
const minimum = String(process.argv[3] || "").trim();
|
|
2454
|
+
if (!minimum) {
|
|
2455
|
+
process.exit(0);
|
|
2456
|
+
}
|
|
2457
|
+
if (!current) {
|
|
2458
|
+
process.exit(1);
|
|
2459
|
+
}
|
|
2460
|
+
function parse(version) {
|
|
2461
|
+
const match = version.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:[-+][0-9A-Za-z.-]+)?$/);
|
|
2462
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
2463
|
+
}
|
|
2464
|
+
const parsedCurrent = parse(current);
|
|
2465
|
+
const parsedMinimum = parse(minimum);
|
|
2466
|
+
if (!parsedCurrent || !parsedMinimum) {
|
|
2467
|
+
process.exit(current === minimum ? 0 : 1);
|
|
2468
|
+
}
|
|
2469
|
+
for (let index = 0; index < 3; index += 1) {
|
|
2470
|
+
if (parsedCurrent[index] > parsedMinimum[index]) {
|
|
2471
|
+
process.exit(0);
|
|
2472
|
+
}
|
|
2473
|
+
if (parsedCurrent[index] < parsedMinimum[index]) {
|
|
2474
|
+
process.exit(1);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
process.exit(0);
|
|
2478
|
+
NODE
|
|
2479
|
+
}
|
|
2166
2480
|
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
run_or_fail "worker_update_plugin_install_failed:codex" playdrop agents update-plugin codex
|
|
2170
|
-
fi
|
|
2171
|
-
if command -v claude >/dev/null 2>&1; then
|
|
2172
|
-
run_or_fail "worker_update_plugin_install_failed:claude" playdrop agents update-plugin claude
|
|
2173
|
-
fi
|
|
2174
|
-
node - "$TARGET_PLUGIN_VERSION" <<'NODE' || fail_converge "$?" "worker_update_plugin_version_mismatch"
|
|
2481
|
+
check_plugin_versions() {
|
|
2482
|
+
node - "$1" "$2" <<'NODE'
|
|
2175
2483
|
const childProcess = require("child_process");
|
|
2176
|
-
const
|
|
2484
|
+
const requiredVersion = String(process.argv[2] || "").trim();
|
|
2485
|
+
const mode = String(process.argv[3] || "").trim();
|
|
2486
|
+
if (!requiredVersion) {
|
|
2487
|
+
process.exit(0);
|
|
2488
|
+
}
|
|
2489
|
+
function parse(version) {
|
|
2490
|
+
const match = version.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:[-+][0-9A-Za-z.-]+)?$/);
|
|
2491
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
2492
|
+
}
|
|
2493
|
+
function meetsMinimum(current, minimum) {
|
|
2494
|
+
const parsedCurrent = parse(current);
|
|
2495
|
+
const parsedMinimum = parse(minimum);
|
|
2496
|
+
if (!parsedCurrent || !parsedMinimum) {
|
|
2497
|
+
return current === minimum;
|
|
2498
|
+
}
|
|
2499
|
+
for (let index = 0; index < 3; index += 1) {
|
|
2500
|
+
if (parsedCurrent[index] > parsedMinimum[index]) {
|
|
2501
|
+
return true;
|
|
2502
|
+
}
|
|
2503
|
+
if (parsedCurrent[index] < parsedMinimum[index]) {
|
|
2504
|
+
return false;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
return true;
|
|
2508
|
+
}
|
|
2177
2509
|
const result = childProcess.spawnSync("playdrop", ["agents", "status", "--json"], { encoding: "utf8" });
|
|
2178
2510
|
if (result.status !== 0) {
|
|
2179
2511
|
process.exit(1);
|
|
@@ -2188,11 +2520,55 @@ const agents = Array.isArray(parsed && parsed.agents) ? parsed.agents : [];
|
|
|
2188
2520
|
const checked = agents.filter((agent) => agent && agent.id !== "cursor" && agent.cli && agent.cli.installed);
|
|
2189
2521
|
for (const agent of checked) {
|
|
2190
2522
|
const version = agent && agent.plugin && typeof agent.plugin.version === "string" ? agent.plugin.version.trim() : "";
|
|
2191
|
-
if (
|
|
2523
|
+
if (mode === "target") {
|
|
2524
|
+
if (version !== requiredVersion) {
|
|
2525
|
+
process.exit(1);
|
|
2526
|
+
}
|
|
2527
|
+
} else if (!meetsMinimum(version, requiredVersion)) {
|
|
2192
2528
|
process.exit(1);
|
|
2193
2529
|
}
|
|
2194
2530
|
}
|
|
2195
2531
|
NODE
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
validate_policy_file
|
|
2535
|
+
TARGET_CLI_VERSION="$(read_policy_value targetCliVersion)"
|
|
2536
|
+
MINIMUM_CLI_VERSION="$(read_policy_value minimumCliVersion)"
|
|
2537
|
+
TARGET_PLUGIN_VERSION="$(read_policy_value targetPluginVersion)"
|
|
2538
|
+
MINIMUM_PLUGIN_VERSION="$(read_policy_value minimumPluginVersion)"
|
|
2539
|
+
|
|
2540
|
+
if [ -n "$TARGET_CLI_VERSION" ]; then
|
|
2541
|
+
if npm install -g --no-audit --no-fund "@playdrop/playdrop-cli@$TARGET_CLI_VERSION"; then
|
|
2542
|
+
CURRENT_CLI_VERSION="$(current_cli_version)" || CURRENT_CLI_VERSION=""
|
|
2543
|
+
if [ "$CURRENT_CLI_VERSION" != "$TARGET_CLI_VERSION" ]; then
|
|
2544
|
+
record_target_failure "worker_update_cli_version_mismatch"
|
|
2545
|
+
fi
|
|
2546
|
+
else
|
|
2547
|
+
record_target_failure "worker_update_cli_install_failed"
|
|
2548
|
+
fi
|
|
2549
|
+
fi
|
|
2550
|
+
|
|
2551
|
+
if [ -n "$TARGET_PLUGIN_VERSION" ]; then
|
|
2552
|
+
if command -v codex >/dev/null 2>&1; then
|
|
2553
|
+
playdrop agents update-plugin codex || record_target_failure "worker_update_plugin_install_failed:codex"
|
|
2554
|
+
fi
|
|
2555
|
+
if command -v claude >/dev/null 2>&1; then
|
|
2556
|
+
playdrop agents update-plugin claude || record_target_failure "worker_update_plugin_install_failed:claude"
|
|
2557
|
+
fi
|
|
2558
|
+
check_plugin_versions "$TARGET_PLUGIN_VERSION" target || record_target_failure "worker_update_plugin_version_mismatch"
|
|
2559
|
+
fi
|
|
2560
|
+
|
|
2561
|
+
if [ -n "$MINIMUM_CLI_VERSION" ]; then
|
|
2562
|
+
CURRENT_CLI_VERSION="$(current_cli_version)" || fail_converge "$?" "worker_update_cli_below_minimum"
|
|
2563
|
+
version_meets "$CURRENT_CLI_VERSION" "$MINIMUM_CLI_VERSION" || fail_converge "$?" "worker_update_cli_below_minimum"
|
|
2564
|
+
fi
|
|
2565
|
+
|
|
2566
|
+
if [ -n "$MINIMUM_PLUGIN_VERSION" ]; then
|
|
2567
|
+
check_plugin_versions "$MINIMUM_PLUGIN_VERSION" minimum || fail_converge "$?" "worker_update_plugin_below_minimum"
|
|
2568
|
+
fi
|
|
2569
|
+
|
|
2570
|
+
if [ "$TARGET_UPDATE_FAILED" -eq 0 ]; then
|
|
2571
|
+
rm -f "$FAILURE_FILE"
|
|
2196
2572
|
fi
|
|
2197
2573
|
|
|
2198
2574
|
node - "$POLICY_FILE" <<'NODE' || fail_converge "$?" "worker_update_agent_cli_install_failed"
|
|
@@ -2711,6 +3087,7 @@ async function startWorker(options = {}) {
|
|
|
2711
3087
|
let eventDrainTimer = null;
|
|
2712
3088
|
let eventDrainPromise = Promise.resolve();
|
|
2713
3089
|
let fenced = false;
|
|
3090
|
+
let heartbeatObservedFinalizedTask = false;
|
|
2714
3091
|
let retainWorkspace = false;
|
|
2715
3092
|
let workspaceDir = null;
|
|
2716
3093
|
let availableSkillPaths = [];
|
|
@@ -2723,6 +3100,7 @@ async function startWorker(options = {}) {
|
|
|
2723
3100
|
let agentCompletedAt = null;
|
|
2724
3101
|
let agentResult = null;
|
|
2725
3102
|
let telemetryReported = false;
|
|
3103
|
+
let heartbeatTransientFailures = 0;
|
|
2726
3104
|
const reportTelemetry = async (status, setupError) => {
|
|
2727
3105
|
if (!assignment) {
|
|
2728
3106
|
return;
|
|
@@ -2836,19 +3214,49 @@ async function startWorker(options = {}) {
|
|
|
2836
3214
|
heartbeatTimer = setInterval(() => {
|
|
2837
3215
|
client.workerHeartbeatAgentTask(task.id, { workerKey, leaseToken })
|
|
2838
3216
|
.then((heartbeat) => {
|
|
3217
|
+
heartbeatTransientFailures = 0;
|
|
2839
3218
|
if (heartbeat.action === 'abort') {
|
|
2840
3219
|
fenced = true;
|
|
2841
3220
|
activeTerminators.get(task.id)?.();
|
|
2842
3221
|
}
|
|
2843
3222
|
})
|
|
2844
3223
|
.catch((error) => {
|
|
2845
|
-
|
|
3224
|
+
const classification = classifyWorkerHeartbeatError(error);
|
|
3225
|
+
if (classification === 'session_expired') {
|
|
2846
3226
|
handleSessionExpiry();
|
|
2847
3227
|
return;
|
|
2848
3228
|
}
|
|
2849
|
-
if (
|
|
2850
|
-
|
|
2851
|
-
|
|
3229
|
+
if (classification === 'lease_invalid') {
|
|
3230
|
+
void (async () => {
|
|
3231
|
+
const refreshed = await fetchTaskDetail(client, target, task.id).catch(() => null);
|
|
3232
|
+
if (refreshed?.task.status === 'DONE') {
|
|
3233
|
+
fenced = true;
|
|
3234
|
+
if (!heartbeatObservedFinalizedTask) {
|
|
3235
|
+
heartbeatObservedFinalizedTask = true;
|
|
3236
|
+
console.error(`Agent task ${task.id} lease ended after task completed as DONE; waiting for agent exit.`);
|
|
3237
|
+
}
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
if (refreshed && refreshed.task.status !== 'RUNNING' && refreshed.task.status !== 'QUEUED') {
|
|
3241
|
+
console.error(`Agent task ${task.id} lease ended after task finalized as ${refreshed.task.status}; terminating agent child.`);
|
|
3242
|
+
}
|
|
3243
|
+
fenced = true;
|
|
3244
|
+
activeTerminators.get(task.id)?.();
|
|
3245
|
+
})().catch((heartbeatError) => {
|
|
3246
|
+
fenced = true;
|
|
3247
|
+
activeTerminators.get(task.id)?.();
|
|
3248
|
+
console.error(`Agent task lease check failed: ${heartbeatError instanceof Error ? heartbeatError.message : String(heartbeatError)}`);
|
|
3249
|
+
});
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
if (classification === 'transient_network') {
|
|
3253
|
+
heartbeatTransientFailures += 1;
|
|
3254
|
+
console.error(`Agent task heartbeat transient failure ${heartbeatTransientFailures}/${TASK_HEARTBEAT_TRANSIENT_FAILURE_LIMIT}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3255
|
+
if (heartbeatTransientFailures >= TASK_HEARTBEAT_TRANSIENT_FAILURE_LIMIT) {
|
|
3256
|
+
fenced = true;
|
|
3257
|
+
activeTerminators.get(task.id)?.();
|
|
3258
|
+
console.error(`Agent task ${task.id} heartbeat could not reach the API; terminating agent child.`);
|
|
3259
|
+
}
|
|
2852
3260
|
return;
|
|
2853
3261
|
}
|
|
2854
3262
|
console.error(`Agent task heartbeat failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -3277,9 +3685,11 @@ async function startWorker(options = {}) {
|
|
|
3277
3685
|
}, { env: options.env });
|
|
3278
3686
|
}
|
|
3279
3687
|
async function reportTask(options) {
|
|
3280
|
-
const message = options.message?.trim();
|
|
3281
|
-
|
|
3282
|
-
|
|
3688
|
+
const message = options.message?.trim() || null;
|
|
3689
|
+
const done = options.done?.trim() || null;
|
|
3690
|
+
const current = options.current?.trim() || null;
|
|
3691
|
+
if (!message && !done && !current) {
|
|
3692
|
+
(0, messages_1.printErrorWithHelp)('Task report requires a message, done, or current value.', ['Example: playdrop task report --phase build --done "Built the first loop" --current "Tuning controls"'], { command: 'task report' });
|
|
3283
3693
|
node_process_1.default.exitCode = 1;
|
|
3284
3694
|
return;
|
|
3285
3695
|
}
|
|
@@ -3287,7 +3697,14 @@ async function reportTask(options) {
|
|
|
3287
3697
|
const kind = normalizeTaskEventKind(options.kind);
|
|
3288
3698
|
const phase = options.phase?.trim() || null;
|
|
3289
3699
|
if (resolveWorkerEventQueueDir()) {
|
|
3290
|
-
enqueueWorkerContextEvent({
|
|
3700
|
+
enqueueWorkerContextEvent({
|
|
3701
|
+
kind,
|
|
3702
|
+
phase,
|
|
3703
|
+
...(message ? { message } : {}),
|
|
3704
|
+
...(done ? { done } : {}),
|
|
3705
|
+
...(current ? { current } : {}),
|
|
3706
|
+
...(pct !== null ? { pct } : {}),
|
|
3707
|
+
});
|
|
3291
3708
|
(0, output_1.printSuccess)('Task progress queued.');
|
|
3292
3709
|
return;
|
|
3293
3710
|
}
|
|
@@ -3384,6 +3801,34 @@ async function uploadTask(options = {}) {
|
|
|
3384
3801
|
}
|
|
3385
3802
|
(0, output_1.printSuccess)(`Uploaded ${published.creatorUsername}/${published.appName} version ${published.version}. Run "playdrop task done" to close the task.`);
|
|
3386
3803
|
}
|
|
3804
|
+
async function claimSlugTask(options) {
|
|
3805
|
+
const taskContext = readTaskContextFile();
|
|
3806
|
+
if (taskContext.kind !== 'NEW_GAME' && taskContext.kind !== 'REMIX_GAME') {
|
|
3807
|
+
throw new Error('task_claim_slug_not_allowed_for_kind');
|
|
3808
|
+
}
|
|
3809
|
+
const appName = normalizeTaskClaimSlugAppName(options.appName);
|
|
3810
|
+
const displayName = normalizeTaskClaimSlugDisplayName(options.displayName);
|
|
3811
|
+
const ctx = await resolveTaskCommandContext('task claim-slug', options.env, taskContext);
|
|
3812
|
+
if (!ctx) {
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3815
|
+
const response = await ctx.client.workerClaimAgentTaskSlug(taskContext.taskId, {
|
|
3816
|
+
appName,
|
|
3817
|
+
displayName,
|
|
3818
|
+
});
|
|
3819
|
+
const claimedAppName = typeof response.appName === 'string' && response.appName.trim()
|
|
3820
|
+
? response.appName.trim()
|
|
3821
|
+
: '';
|
|
3822
|
+
if (claimedAppName !== appName) {
|
|
3823
|
+
throw new Error('task_claim_slug_response_mismatch');
|
|
3824
|
+
}
|
|
3825
|
+
const workspaceDir = resolveTaskWorkspaceDir();
|
|
3826
|
+
await writeWorkerTaskContextFile(workspaceDir, {
|
|
3827
|
+
...taskContext,
|
|
3828
|
+
outputAppName: claimedAppName,
|
|
3829
|
+
});
|
|
3830
|
+
(0, output_1.printSuccess)(`Claimed app slug "${claimedAppName}" for ${displayName}.`);
|
|
3831
|
+
}
|
|
3387
3832
|
async function completeTask(options) {
|
|
3388
3833
|
const taskContext = readTaskContextFile();
|
|
3389
3834
|
if (taskContext.kind === 'GAME_REVIEW') {
|
|
@@ -3402,21 +3847,48 @@ async function completeTask(options) {
|
|
|
3402
3847
|
if (uploadResult.taskId !== taskContext.taskId) {
|
|
3403
3848
|
throw new Error('task_done_upload_result_task_mismatch');
|
|
3404
3849
|
}
|
|
3405
|
-
await
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
appId: uploadResult.appId,
|
|
3411
|
-
appVersionId: uploadResult.appVersionId,
|
|
3412
|
-
version: uploadResult.version,
|
|
3413
|
-
appName: uploadResult.appName,
|
|
3414
|
-
versionNodeId: uploadResult.versionNodeId,
|
|
3415
|
-
creatorUsername: uploadResult.creatorUsername,
|
|
3416
|
-
completedBy: 'agent',
|
|
3417
|
-
},
|
|
3850
|
+
const nextSteps = await readTaskNextStepsFileForCommand({
|
|
3851
|
+
filePath: options.nextSteps,
|
|
3852
|
+
command: 'task done',
|
|
3853
|
+
ctx,
|
|
3854
|
+
taskContext,
|
|
3418
3855
|
});
|
|
3419
|
-
|
|
3856
|
+
if (nextSteps === NEXT_STEPS_READ_FAILED) {
|
|
3857
|
+
return;
|
|
3858
|
+
}
|
|
3859
|
+
try {
|
|
3860
|
+
await ctx.client.workerCompleteAgentTask(taskContext.taskId, {
|
|
3861
|
+
taskToken: taskContext.taskToken,
|
|
3862
|
+
appId,
|
|
3863
|
+
appVersionId,
|
|
3864
|
+
result: {
|
|
3865
|
+
appId: uploadResult.appId,
|
|
3866
|
+
appVersionId: uploadResult.appVersionId,
|
|
3867
|
+
version: uploadResult.version,
|
|
3868
|
+
appName: uploadResult.appName,
|
|
3869
|
+
versionNodeId: uploadResult.versionNodeId,
|
|
3870
|
+
creatorUsername: uploadResult.creatorUsername,
|
|
3871
|
+
completedBy: 'agent',
|
|
3872
|
+
},
|
|
3873
|
+
...(nextSteps !== undefined ? { nextSteps } : {}),
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3876
|
+
catch (error) {
|
|
3877
|
+
if (error instanceof types_1.ApiError && error.status === 409 && error.code === 'remix_relation_missing') {
|
|
3878
|
+
await ctx.client.workerFailAgentTask(taskContext.taskId, {
|
|
3879
|
+
taskToken: taskContext.taskToken,
|
|
3880
|
+
error: 'remix_relation_missing',
|
|
3881
|
+
result: {
|
|
3882
|
+
appId: uploadResult.appId,
|
|
3883
|
+
appVersionId: uploadResult.appVersionId,
|
|
3884
|
+
appName: uploadResult.appName,
|
|
3885
|
+
version: uploadResult.version,
|
|
3886
|
+
},
|
|
3887
|
+
});
|
|
3888
|
+
}
|
|
3889
|
+
throw error;
|
|
3890
|
+
}
|
|
3891
|
+
(0, output_1.printSuccess)('Task marked done. Send one short final status sentence now, then stop.');
|
|
3420
3892
|
}
|
|
3421
3893
|
function normalizeReviewSubmitState(value) {
|
|
3422
3894
|
const normalized = typeof value === 'string' ? value.trim().toUpperCase() : '';
|
|
@@ -3545,15 +4017,25 @@ async function submitReviewTask(options) {
|
|
|
3545
4017
|
if (!ctx) {
|
|
3546
4018
|
throw new Error('task_submit_review_auth_required');
|
|
3547
4019
|
}
|
|
4020
|
+
const nextSteps = await readTaskNextStepsFileForCommand({
|
|
4021
|
+
filePath: options.nextSteps,
|
|
4022
|
+
command: 'task submit-review',
|
|
4023
|
+
ctx,
|
|
4024
|
+
taskContext,
|
|
4025
|
+
});
|
|
4026
|
+
if (nextSteps === NEXT_STEPS_READ_FAILED) {
|
|
4027
|
+
return;
|
|
4028
|
+
}
|
|
3548
4029
|
await ctx.client.workerSubmitAgentTaskReview(taskContext.taskId, {
|
|
3549
4030
|
taskToken: taskContext.taskToken,
|
|
3550
4031
|
appVersionId: taskContext.reviewAppVersionId ?? 0,
|
|
3551
4032
|
reviewState: state,
|
|
3552
4033
|
reviewMessage,
|
|
3553
4034
|
creatorFeedback,
|
|
4035
|
+
...(nextSteps !== undefined ? { nextSteps } : {}),
|
|
3554
4036
|
evidenceFiles,
|
|
3555
4037
|
});
|
|
3556
|
-
(0, output_1.printSuccess)('Review submitted.');
|
|
4038
|
+
(0, output_1.printSuccess)('Review submitted. Send one short final status sentence now, then stop.');
|
|
3557
4039
|
}
|
|
3558
4040
|
async function failTask(options) {
|
|
3559
4041
|
const message = options.message?.trim();
|
|
@@ -3572,5 +4054,5 @@ async function failTask(options) {
|
|
|
3572
4054
|
error: message,
|
|
3573
4055
|
result: { failedBy: 'agent' },
|
|
3574
4056
|
});
|
|
3575
|
-
(0, output_1.printSuccess)('Task marked failed.');
|
|
4057
|
+
(0, output_1.printSuccess)('Task marked failed. Send one short final status sentence now, then stop.');
|
|
3576
4058
|
}
|