@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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.10.17",
2
+ "version": "0.10.19",
3
3
  "build": 1,
4
4
  "runtimeSdkVersion": "0.10.0",
5
5
  "clients": {
@@ -19,8 +19,8 @@ async function runAppPipeline(client, task, options) {
19
19
  await (0, validate_1.validateAppTask)(task);
20
20
  // External apps don't need to be built - they're hosted elsewhere
21
21
  const isExternal = task.hostingMode === 'EXTERNAL' || !!task.externalUrl;
22
- const requiresRegisteredShell = !isExternal && task.authMode === 'REQUIRED';
23
- if (requiresRegisteredShell && options?.ensureRegisteredAppShell) {
22
+ const shouldEnsureRegisteredShell = !isExternal && options?.ensureRegisteredAppShell;
23
+ if (shouldEnsureRegisteredShell) {
24
24
  const creatorUsername = typeof options.creatorUsername === 'string' ? options.creatorUsername.trim() : '';
25
25
  if (!creatorUsername) {
26
26
  throw new Error(`Missing creator username for required hosted app registration of "${task.name}".`);
@@ -28,6 +28,7 @@ async function ensureRegisteredHostedAppShell(input) {
28
28
  name: input.task.name,
29
29
  displayName,
30
30
  ...(input.agentTaskId !== undefined ? { agentTaskId: input.agentTaskId } : {}),
31
+ ...(input.task.remix ? { remixRef: input.task.remix } : {}),
31
32
  });
32
33
  app = createdResponse.app;
33
34
  created = true;
@@ -13,6 +13,7 @@ export type RunCaptureOptions = {
13
13
  expectedUrl?: string | null;
14
14
  timeoutMs: number;
15
15
  settleAfterReadyMs?: number;
16
+ frameRateSampleMs?: number;
16
17
  minimumLogLevel: CaptureLogLevel;
17
18
  screenshotPath?: string | null;
18
19
  logPath?: string | null;
@@ -59,6 +60,12 @@ export type CaptureRunResult = {
59
60
  warnings: string[];
60
61
  finalUrl: string;
61
62
  hostedLaunchState: CaptureHostedLaunchState | null;
63
+ frameRate: CaptureFrameRateSample | null;
64
+ };
65
+ export type CaptureFrameRateSample = {
66
+ fps: number;
67
+ sampleMs: number;
68
+ frameCount: number;
62
69
  };
63
70
  export declare function resolveCaptureLogLevel(value: string | undefined): CaptureLogLevel;
64
71
  export declare function validateCaptureTimeout(value: number | undefined): number;
@@ -526,6 +526,36 @@ async function runCaptureActions(page, actions) {
526
526
  throw new Error('capture_action_type_unknown');
527
527
  }
528
528
  }
529
+ async function sampleFrameRate(page, sampleMs) {
530
+ if (!Number.isInteger(sampleMs) || sampleMs <= 0) {
531
+ throw new Error('capture_fps_sample_invalid');
532
+ }
533
+ const childFrames = page.frames().filter(frame => frame.parentFrame() !== null);
534
+ const target = childFrames[0] ?? page;
535
+ const sample = await target.evaluate((durationMs) => new Promise((resolve) => {
536
+ const startedAt = performance.now();
537
+ let frameCount = 0;
538
+ const tick = (timestamp) => {
539
+ frameCount += 1;
540
+ const elapsedMs = Math.max(timestamp - startedAt, 0);
541
+ if (elapsedMs >= durationMs) {
542
+ resolve({
543
+ fps: frameCount > 0 && elapsedMs > 0 ? frameCount * 1000 / elapsedMs : 0,
544
+ sampleMs: elapsedMs,
545
+ frameCount,
546
+ });
547
+ return;
548
+ }
549
+ requestAnimationFrame(tick);
550
+ };
551
+ requestAnimationFrame(tick);
552
+ }), sampleMs);
553
+ return {
554
+ fps: Number(sample.fps.toFixed(2)),
555
+ sampleMs: Math.round(sample.sampleMs),
556
+ frameCount: sample.frameCount,
557
+ };
558
+ }
529
559
  async function assertHostedFrameVisualReadiness(page) {
530
560
  const childFrames = page.frames().filter(frame => frame.parentFrame() !== null);
531
561
  if (childFrames.length === 0) {
@@ -596,6 +626,7 @@ async function runCapture(options) {
596
626
  const bootstrapUser = shouldBootstrapSavedSession ? options.user ?? null : null;
597
627
  let finalUrl = options.targetUrl;
598
628
  let hostedLaunchState = null;
629
+ let frameRate = null;
599
630
  const record = (level, message) => {
600
631
  const line = `[capture][${level}] ${message}`;
601
632
  outputLines.push(line);
@@ -959,6 +990,10 @@ async function runCapture(options) {
959
990
  await page.screenshot({ path: options.screenshotPath, fullPage: true });
960
991
  console.log(`[capture] Saved screenshot to ${(0, node_path_1.relative)(process.cwd(), options.screenshotPath) || options.screenshotPath}`);
961
992
  }
993
+ if (options.frameRateSampleMs && options.frameRateSampleMs > 0) {
994
+ frameRate = await sampleFrameRate(page, options.frameRateSampleMs);
995
+ record('info', `Frame-rate sample: ${frameRate.fps.toFixed(2)} fps over ${frameRate.sampleMs}ms (${frameRate.frameCount} frames).`);
996
+ }
962
997
  }, {
963
998
  ...(options.contextOptions ?? {}),
964
999
  automationOrigin: resolveAutomationOrigin(options.targetUrl),
@@ -974,5 +1009,6 @@ async function runCapture(options) {
974
1009
  warnings,
975
1010
  finalUrl,
976
1011
  hostedLaunchState,
1012
+ frameRate,
977
1013
  };
978
1014
  }
@@ -1,5 +1,6 @@
1
1
  type CaptureRemoteOptions = {
2
2
  timeout?: string | number;
3
+ settle?: string | number;
3
4
  screenshot?: string;
4
5
  log?: string;
5
6
  expectedUrl?: string;
@@ -8,6 +9,7 @@ type CaptureRemoteOptions = {
8
9
  loginUrl?: string;
9
10
  actions?: string;
10
11
  viewport?: string;
12
+ fpsSampleMs?: string | number;
11
13
  };
12
14
  export declare function buildCaptureRemoteCommandArgs(url: string | undefined, options?: CaptureRemoteOptions): string[] | null;
13
15
  export declare function captureRemote(url: string | undefined, options?: CaptureRemoteOptions): Promise<void>;
@@ -43,6 +43,16 @@ function parseTimeout(raw) {
43
43
  }
44
44
  return parsed;
45
45
  }
46
+ function parseSettle(raw) {
47
+ if (raw === undefined) {
48
+ return 1;
49
+ }
50
+ const parsed = typeof raw === 'number' ? raw : Number.parseFloat(String(raw));
51
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 600) {
52
+ throw new Error('invalid_settle');
53
+ }
54
+ return parsed;
55
+ }
46
56
  function parseViewport(raw) {
47
57
  const value = raw?.trim();
48
58
  if (!value) {
@@ -59,6 +69,16 @@ function parseViewport(raw) {
59
69
  }
60
70
  return { width, height };
61
71
  }
72
+ function parseFpsSampleMs(raw) {
73
+ if (raw === undefined) {
74
+ return null;
75
+ }
76
+ const parsed = typeof raw === 'number' ? raw : Number.parseFloat(String(raw));
77
+ if (!Number.isInteger(parsed) || parsed < 100 || parsed > 60000) {
78
+ throw new Error('invalid_fps_sample_ms');
79
+ }
80
+ return parsed;
81
+ }
62
82
  function normalizeCaptureAction(raw, index) {
63
83
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
64
84
  throw new Error(`invalid_action:${index}`);
@@ -144,11 +164,17 @@ function buildCaptureRemoteCommandArgs(url, options = {}) {
144
164
  if (options.timeout !== undefined) {
145
165
  args.push('--timeout', String(parseTimeout(options.timeout)));
146
166
  }
167
+ if (options.settle !== undefined) {
168
+ args.push('--settle', String(parseSettle(options.settle)));
169
+ }
147
170
  pushOptionalArg(args, '--screenshot', options.screenshot);
148
171
  pushOptionalArg(args, '--log', options.log);
149
172
  pushOptionalArg(args, '--expected-url', options.expectedUrl);
150
173
  pushOptionalArg(args, '--actions', options.actions);
151
174
  pushOptionalArg(args, '--viewport', options.viewport);
175
+ if (options.fpsSampleMs !== undefined) {
176
+ args.push('--fps-sample-ms', String(parseFpsSampleMs(options.fpsSampleMs)));
177
+ }
152
178
  if (username) {
153
179
  args.push('--username', username, '--password', password);
154
180
  }
@@ -174,10 +200,12 @@ function parseOptions(url, options = {}) {
174
200
  targetUrl: trimmedUrl,
175
201
  expectedUrl,
176
202
  timeoutSeconds: parseTimeout(options.timeout),
203
+ settleSeconds: parseSettle(options.settle),
177
204
  screenshotPath: (0, node_path_1.resolve)(process.cwd(), options.screenshot?.trim() || 'output/playwright/capture-url.png'),
178
205
  logPath: options.log?.trim() ? (0, node_path_1.resolve)(process.cwd(), options.log.trim()) : null,
179
206
  actionsPath: options.actions?.trim() ? (0, node_path_1.resolve)(process.cwd(), options.actions.trim()) : null,
180
207
  viewport: parseViewport(options.viewport),
208
+ fpsSampleMs: parseFpsSampleMs(options.fpsSampleMs),
181
209
  login: username
182
210
  ? {
183
211
  username,
@@ -209,11 +237,21 @@ async function captureRemote(url, options = {}) {
209
237
  process.exitCode = 1;
210
238
  return;
211
239
  }
240
+ if (error instanceof Error && error.message === 'invalid_settle') {
241
+ (0, messages_1.printErrorWithHelp)('The --settle value must be a number between 0 and 600 seconds.', [], { command: 'project capture remote' });
242
+ process.exitCode = 1;
243
+ return;
244
+ }
212
245
  if (error instanceof Error && error.message === 'invalid_viewport') {
213
246
  (0, messages_1.printErrorWithHelp)('The --viewport value must be WIDTHxHEIGHT with each side between 320 and 4096 pixels.', [], { command: 'project capture remote' });
214
247
  process.exitCode = 1;
215
248
  return;
216
249
  }
250
+ if (error instanceof Error && error.message === 'invalid_fps_sample_ms') {
251
+ (0, messages_1.printErrorWithHelp)('The --fps-sample-ms value must be an integer between 100 and 60000 milliseconds.', [], { command: 'project capture remote' });
252
+ process.exitCode = 1;
253
+ return;
254
+ }
217
255
  if (error instanceof Error && error.message === 'invalid_credentials_pair') {
218
256
  (0, messages_1.printErrorWithHelp)('Use --username and --password together.', [], { command: 'project capture remote' });
219
257
  process.exitCode = 1;
@@ -272,6 +310,7 @@ async function captureRemote(url, options = {}) {
272
310
  targetUrl: parsed.targetUrl,
273
311
  expectedUrl: parsed.expectedUrl ?? parsed.targetUrl,
274
312
  timeoutMs: Math.round(parsed.timeoutSeconds * 1000),
313
+ settleAfterReadyMs: Math.round(parsed.settleSeconds * 1000),
275
314
  minimumLogLevel: 'info',
276
315
  screenshotPath: parsed.screenshotPath,
277
316
  logPath: parsed.logPath,
@@ -280,13 +319,14 @@ async function captureRemote(url, options = {}) {
280
319
  login: parsed.login,
281
320
  actions: actions ?? undefined,
282
321
  contextOptions: parsed.viewport ? { viewport: parsed.viewport } : undefined,
322
+ frameRateSampleMs: parsed.fpsSampleMs ?? undefined,
283
323
  });
284
324
  if (result.errorCount > 0) {
285
- console.error(`[capture] Completed with ${result.errorCount} error(s) after ${parsed.timeoutSeconds} seconds.`);
325
+ console.error(`[capture] Completed with ${result.errorCount} error(s) after timeout=${parsed.timeoutSeconds}s settle=${parsed.settleSeconds}s.`);
286
326
  process.exitCode = 1;
287
327
  }
288
328
  else {
289
- console.log(`[capture] Completed without console errors after ${parsed.timeoutSeconds} seconds.`);
329
+ console.log(`[capture] Completed without console errors after timeout=${parsed.timeoutSeconds}s settle=${parsed.settleSeconds}s.`);
290
330
  }
291
331
  }
292
332
  catch (error) {
@@ -73,9 +73,14 @@ async function observeManagedWorkerUpdate(input) {
73
73
  }
74
74
  if (worker.activeUpdateIntentId !== input.intentId
75
75
  && worker.lifecycleState === 'READY'
76
- && worker.updateState === 'CURRENT'
76
+ && (worker.updateState === 'CURRENT' || worker.updateState === 'UPDATE_AVAILABLE')
77
77
  && worker.ready === true) {
78
- console.log('Managed worker update completed.');
78
+ if (worker.updateState === 'UPDATE_AVAILABLE') {
79
+ console.log('Managed worker update settled above minimum with a target update still available.');
80
+ }
81
+ else {
82
+ console.log('Managed worker update completed.');
83
+ }
79
84
  return;
80
85
  }
81
86
  await sleep(pollMs);
@@ -28,6 +28,13 @@ export type LoggedProcessTranscriptChunk = {
28
28
  export declare function readPositiveEnvInt(name: string, fallback: number): number;
29
29
  export declare function readEnvBoolean(name: string, fallback: boolean): boolean;
30
30
  export declare function extractAgentTokenUsage(output: string): AgentTokenUsage;
31
+ export declare function extractCodexRolloutTokenUsage(output: string): AgentTokenUsage;
32
+ export declare function extractCodexThreadId(output: string): string | null;
33
+ export type CodexRolloutTokenUsageReadOptions = {
34
+ sessionsRoot?: string;
35
+ deleteAfterRead?: boolean;
36
+ };
37
+ export declare function readCodexRolloutTokenUsageForThread(threadId: string, options?: CodexRolloutTokenUsageReadOptions): Promise<AgentTokenUsage>;
31
38
  export declare function extractCodexTokensUsed(output: string): number | null;
32
39
  export declare function assertWorkerTokenUsageWithinCap(input: {
33
40
  tokensUsed: number | null;
@@ -7,6 +7,9 @@ exports.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS = exports.DEFAULT_CODEX_LOG_TAIL_CH
7
7
  exports.readPositiveEnvInt = readPositiveEnvInt;
8
8
  exports.readEnvBoolean = readEnvBoolean;
9
9
  exports.extractAgentTokenUsage = extractAgentTokenUsage;
10
+ exports.extractCodexRolloutTokenUsage = extractCodexRolloutTokenUsage;
11
+ exports.extractCodexThreadId = extractCodexThreadId;
12
+ exports.readCodexRolloutTokenUsageForThread = readCodexRolloutTokenUsageForThread;
10
13
  exports.extractCodexTokensUsed = extractCodexTokensUsed;
11
14
  exports.assertWorkerTokenUsageWithinCap = assertWorkerTokenUsageWithinCap;
12
15
  exports.runLoggedProcess = runLoggedProcess;
@@ -17,6 +20,8 @@ exports.buildClaudePermissionSettings = buildClaudePermissionSettings;
17
20
  exports.buildClaudeDeniedPermissionRules = buildClaudeDeniedPermissionRules;
18
21
  exports.buildWorkerChildEnv = buildWorkerChildEnv;
19
22
  const node_child_process_1 = require("node:child_process");
23
+ const promises_1 = require("node:fs/promises");
24
+ const node_os_1 = __importDefault(require("node:os"));
20
25
  const node_path_1 = __importDefault(require("node:path"));
21
26
  const CHILD_KILL_GRACE_MS = 10000;
22
27
  exports.DEFAULT_CODEX_TIMEOUT_MS = 2 * 60 * 60 * 1000;
@@ -59,6 +64,15 @@ function normalizeTokenCount(value) {
59
64
  }
60
65
  return Math.ceil(value);
61
66
  }
67
+ function normalizeFirstTokenCount(...values) {
68
+ for (const value of values) {
69
+ const normalized = normalizeTokenCount(value);
70
+ if (normalized !== null) {
71
+ return normalized;
72
+ }
73
+ }
74
+ return null;
75
+ }
62
76
  function coerceRecord(value) {
63
77
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
64
78
  return null;
@@ -91,6 +105,55 @@ function readStructuredUsage(parsed) {
91
105
  }
92
106
  return null;
93
107
  }
108
+ function readCodexRolloutStructuredUsage(parsed) {
109
+ const root = coerceRecord(parsed);
110
+ if (!root || root.type !== 'token_count') {
111
+ return null;
112
+ }
113
+ const info = coerceRecord(root.info);
114
+ return coerceRecord(info?.total_token_usage);
115
+ }
116
+ function normalizeAgentTokenUsageFromRecord(usage) {
117
+ const outputTokens = normalizeFirstTokenCount(usage.output_tokens, usage.outputTokens);
118
+ const cacheCreationInputTokens = normalizeFirstTokenCount(usage.cache_creation_input_tokens, usage.cacheCreationInputTokens);
119
+ const cacheReadInputTokens = normalizeFirstTokenCount(usage.cache_read_input_tokens, usage.cached_input_tokens, usage.cacheReadInputTokens);
120
+ const rawInputTokens = normalizeFirstTokenCount(usage.input_tokens, usage.inputTokens);
121
+ const inputTokens = rawInputTokens !== null
122
+ && cacheReadInputTokens !== null
123
+ && usage.cached_input_tokens !== undefined
124
+ ? Math.max(0, rawInputTokens - cacheReadInputTokens)
125
+ : rawInputTokens;
126
+ const tokenParts = [
127
+ inputTokens,
128
+ outputTokens,
129
+ cacheCreationInputTokens,
130
+ cacheReadInputTokens,
131
+ ];
132
+ const sawTokenValue = tokenParts.some((value) => value !== null);
133
+ if (!sawTokenValue) {
134
+ return null;
135
+ }
136
+ return {
137
+ inputTokens,
138
+ outputTokens,
139
+ cacheCreationInputTokens,
140
+ cacheReadInputTokens,
141
+ totalTokens: tokenParts.reduce((sum, value) => sum + (value ?? 0), 0),
142
+ rawProviderUsage: usage,
143
+ usageParseError: null,
144
+ };
145
+ }
146
+ function missingAgentTokenUsage(reason = 'agent_usage_not_reported') {
147
+ return {
148
+ inputTokens: null,
149
+ outputTokens: null,
150
+ cacheCreationInputTokens: null,
151
+ cacheReadInputTokens: null,
152
+ totalTokens: null,
153
+ rawProviderUsage: null,
154
+ usageParseError: reason,
155
+ };
156
+ }
94
157
  function extractAgentTokenUsage(output) {
95
158
  for (const line of output.split(/\r?\n/).reverse()) {
96
159
  const trimmed = line.trim();
@@ -103,42 +166,119 @@ function extractAgentTokenUsage(output) {
103
166
  if (!usage) {
104
167
  continue;
105
168
  }
106
- const inputTokens = normalizeTokenCount(usage.input_tokens);
107
- const outputTokens = normalizeTokenCount(usage.output_tokens);
108
- const cacheCreationInputTokens = normalizeTokenCount(usage.cache_creation_input_tokens);
109
- const cacheReadInputTokens = normalizeTokenCount(usage.cache_read_input_tokens);
110
- const tokenParts = [
111
- inputTokens,
112
- outputTokens,
113
- cacheCreationInputTokens,
114
- cacheReadInputTokens,
115
- ];
116
- const sawTokenValue = tokenParts.some((value) => value !== null);
117
- if (sawTokenValue) {
118
- return {
119
- inputTokens,
120
- outputTokens,
121
- cacheCreationInputTokens,
122
- cacheReadInputTokens,
123
- totalTokens: tokenParts.reduce((sum, value) => sum + (value ?? 0), 0),
124
- rawProviderUsage: usage,
125
- usageParseError: null,
126
- };
169
+ const tokenUsage = normalizeAgentTokenUsageFromRecord(usage);
170
+ if (tokenUsage) {
171
+ return tokenUsage;
127
172
  }
128
173
  }
129
174
  catch {
130
175
  continue;
131
176
  }
132
177
  }
133
- return {
134
- inputTokens: null,
135
- outputTokens: null,
136
- cacheCreationInputTokens: null,
137
- cacheReadInputTokens: null,
138
- totalTokens: null,
139
- rawProviderUsage: null,
140
- usageParseError: 'agent_usage_not_reported',
141
- };
178
+ return missingAgentTokenUsage();
179
+ }
180
+ function extractCodexRolloutTokenUsage(output) {
181
+ for (const line of output.split(/\r?\n/).reverse()) {
182
+ const trimmed = line.trim();
183
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
184
+ continue;
185
+ }
186
+ try {
187
+ const parsed = JSON.parse(trimmed);
188
+ const usage = readCodexRolloutStructuredUsage(parsed);
189
+ if (!usage) {
190
+ continue;
191
+ }
192
+ const tokenUsage = normalizeAgentTokenUsageFromRecord(usage);
193
+ if (tokenUsage) {
194
+ return tokenUsage;
195
+ }
196
+ }
197
+ catch {
198
+ continue;
199
+ }
200
+ }
201
+ return missingAgentTokenUsage();
202
+ }
203
+ function extractCodexThreadId(output) {
204
+ for (const line of output.split(/\r?\n/)) {
205
+ const trimmed = line.trim();
206
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
207
+ continue;
208
+ }
209
+ try {
210
+ const root = coerceRecord(JSON.parse(trimmed));
211
+ if (!root) {
212
+ continue;
213
+ }
214
+ const type = typeof root.type === 'string' ? root.type : '';
215
+ const rawThreadId = type === 'thread.started'
216
+ ? root.thread_id ?? root.threadId
217
+ : type === 'session_meta'
218
+ ? root.session_id ?? root.sessionId
219
+ : null;
220
+ if (typeof rawThreadId === 'string' && rawThreadId.trim()) {
221
+ return rawThreadId.trim();
222
+ }
223
+ }
224
+ catch {
225
+ continue;
226
+ }
227
+ }
228
+ return null;
229
+ }
230
+ async function findCodexRolloutFileForThread(threadId, sessionsRoot) {
231
+ const stack = [{ dir: sessionsRoot, depth: 0 }];
232
+ const matches = [];
233
+ while (stack.length > 0) {
234
+ const current = stack.pop();
235
+ let entries;
236
+ try {
237
+ entries = await (0, promises_1.readdir)(current.dir, { withFileTypes: true });
238
+ }
239
+ catch (error) {
240
+ if (error.code === 'ENOENT') {
241
+ continue;
242
+ }
243
+ throw error;
244
+ }
245
+ for (const entry of entries) {
246
+ const entryPath = node_path_1.default.join(current.dir, entry.name);
247
+ if (entry.isDirectory()) {
248
+ if (current.depth < 6) {
249
+ stack.push({ dir: entryPath, depth: current.depth + 1 });
250
+ }
251
+ continue;
252
+ }
253
+ if (entry.isFile()
254
+ && entry.name.startsWith('rollout-')
255
+ && entry.name.endsWith('.jsonl')
256
+ && entry.name.includes(threadId)) {
257
+ matches.push(entryPath);
258
+ }
259
+ }
260
+ }
261
+ if (matches.length <= 0) {
262
+ return null;
263
+ }
264
+ matches.sort();
265
+ return matches[matches.length - 1];
266
+ }
267
+ async function readCodexRolloutTokenUsageForThread(threadId, options = {}) {
268
+ const normalizedThreadId = threadId.trim();
269
+ if (!normalizedThreadId) {
270
+ return missingAgentTokenUsage('codex_thread_id_missing');
271
+ }
272
+ const sessionsRoot = options.sessionsRoot ?? node_path_1.default.join(node_os_1.default.homedir(), '.codex', 'sessions');
273
+ const rolloutPath = await findCodexRolloutFileForThread(normalizedThreadId, sessionsRoot);
274
+ if (!rolloutPath) {
275
+ return missingAgentTokenUsage('codex_rollout_file_not_found');
276
+ }
277
+ const output = await (0, promises_1.readFile)(rolloutPath, 'utf8');
278
+ if (options.deleteAfterRead !== false) {
279
+ await (0, promises_1.rm)(rolloutPath, { force: true });
280
+ }
281
+ return extractCodexRolloutTokenUsage(output);
142
282
  }
143
283
  function extractCodexTokensUsed(output) {
144
284
  return extractAgentTokenUsage(output).totalTokens;
@@ -285,12 +425,12 @@ function buildCodexExecArgs(input) {
285
425
  const sandboxMode = input.sandboxMode ?? 'danger-full-access';
286
426
  const args = [
287
427
  'exec',
428
+ '--json',
288
429
  '--sandbox',
289
430
  sandboxMode,
290
431
  '--cd',
291
432
  input.workspaceDir,
292
433
  '--skip-git-repo-check',
293
- '--ephemeral',
294
434
  '--model',
295
435
  input.model,
296
436
  '-c',
@@ -1,11 +1,11 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
2
- import { type AgentExecutionTarget, type AgentRuntime, type AgentTaskKind, type AgentTaskResponse, type AgentWorkerCapabilities, type WorkerAgentTaskAssignmentResponse, type WorkerAgentTaskBaseSourceResponse, type WorkerAgentTaskWorkspaceFileResponse, type WorkerClaimAgentTaskResponse } from '@playdrop/types';
2
+ import { type AgentTaskNextStepSuggestion, type AgentExecutionTarget, type AgentRuntime, type AgentTaskKind, type AgentTaskResponse, type AgentWorkerCapabilities, type WorkerAgentTaskAssignmentResponse, type WorkerAgentTaskBaseSourceResponse, type WorkerAgentTaskWorkspaceFileResponse, type WorkerClaimAgentTaskResponse } from '@playdrop/types';
3
3
  import { type WorkerPlaydropAssetRequirement } from './upload';
4
4
  import { type LoggedProcessResult, type LoggedProcessTranscriptChunk } from './worker/runtime';
5
- export { DEFAULT_CODEX_TIMEOUT_MS, DEFAULT_WORKER_TOKEN_CAP, assertWorkerTokenUsageWithinCap, buildCodexExecArgs, buildClaudeExecArgs, buildClaudePermissionSettings, buildWorkerChildEnv, extractAgentTokenUsage, extractCodexTokensUsed, readEnvBoolean, readCodexSandboxMode, runLoggedProcess, } from './worker/runtime';
5
+ export { DEFAULT_CODEX_TIMEOUT_MS, DEFAULT_WORKER_TOKEN_CAP, assertWorkerTokenUsageWithinCap, buildCodexExecArgs, buildClaudeExecArgs, buildClaudePermissionSettings, buildWorkerChildEnv, extractAgentTokenUsage, extractCodexRolloutTokenUsage, extractCodexThreadId, extractCodexTokensUsed, readEnvBoolean, readCodexSandboxMode, readCodexRolloutTokenUsageForThread, runLoggedProcess, } from './worker/runtime';
6
6
  export type { AgentTokenUsage, LoggedProcessResult, LoggedProcessTranscriptChunk, } from './worker/runtime';
7
7
  export declare const WORKER_SESSION_EXPIRED_MESSAGE = "worker session expired: run \"playdrop auth login\" and start the worker again";
8
- export declare const WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = "worker_context_command_not_allowed: inside a PlayDrop worker task only task progress, task upload/done/fail, assigned remix scaffolding, project validation/build/dev/capture, read-only catalogue/documentation lookup, and PlayDrop AI generation are permitted.";
8
+ export declare const 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.";
9
9
  type WorkerStartOptions = {
10
10
  env?: string;
11
11
  once?: boolean;
@@ -57,6 +57,8 @@ type TaskReportOptions = {
57
57
  phase?: string;
58
58
  pct?: string | number;
59
59
  message?: string;
60
+ done?: string;
61
+ current?: string;
60
62
  kind?: string;
61
63
  };
62
64
  type TaskCatalogueReportOptions = {
@@ -66,6 +68,12 @@ type TaskCatalogueReportOptions = {
66
68
  };
67
69
  type TaskCompleteOptions = {
68
70
  env?: string;
71
+ nextSteps?: string;
72
+ };
73
+ type TaskClaimSlugOptions = {
74
+ appName?: string;
75
+ displayName?: string;
76
+ env?: string;
69
77
  };
70
78
  type TaskUploadOptions = {
71
79
  env?: string;
@@ -80,6 +88,7 @@ type TaskSubmitReviewOptions = {
80
88
  messageFile?: string;
81
89
  creatorFeedbackFile?: string;
82
90
  evidenceDir?: string;
91
+ nextSteps?: string;
83
92
  };
84
93
  export type WorkerHealthAlertInput = {
85
94
  state: 'started' | 'stopped' | 'crashed';
@@ -115,6 +124,7 @@ export declare function appendWorkerTranscriptChunks(input: {
115
124
  chunks: LoggedProcessTranscriptChunk[];
116
125
  batchSize?: number;
117
126
  }): Promise<void>;
127
+ export declare function readTaskNextStepsFile(filePath: string | undefined): AgentTaskNextStepSuggestion[] | undefined;
118
128
  export declare function assertTaskUploadResultMatchesContext(input: {
119
129
  taskContext: {
120
130
  taskId: number;
@@ -144,6 +154,8 @@ export declare function readWorkerTaskState(): WorkerTaskState | null;
144
154
  export declare function buildWorkerHealthAlertText(input: WorkerHealthAlertInput): string;
145
155
  export declare function isWorkerAuthFailureError(error: unknown): boolean;
146
156
  export declare function classifyWorkerEventDrainError(error: unknown): 'session_expired' | 'lease_invalid' | null;
157
+ export declare function isWorkerTransientNetworkError(error: unknown): boolean;
158
+ export declare function classifyWorkerHeartbeatError(error: unknown): 'session_expired' | 'lease_invalid' | 'transient_network' | null;
147
159
  export declare function assertWorkerClaimErrorRetryable(error: unknown): void;
148
160
  export declare function resolvePlaydropAssetRequirementFromText(value: unknown): WorkerPlaydropAssetRequirement | null;
149
161
  export declare function loadWorkerEnvFile(envName: string | undefined, cwd?: string): string | null;
@@ -218,6 +230,7 @@ export declare function startWorker(options?: WorkerStartOptions): Promise<void>
218
230
  export declare function reportTask(options: TaskReportOptions): Promise<void>;
219
231
  export declare function reportCatalogueTask(options: TaskCatalogueReportOptions): Promise<void>;
220
232
  export declare function uploadTask(options?: TaskUploadOptions): Promise<void>;
233
+ export declare function claimSlugTask(options: TaskClaimSlugOptions): Promise<void>;
221
234
  export declare function completeTask(options: TaskCompleteOptions): Promise<void>;
222
235
  export declare function readReviewEvidenceFiles(evidenceDir: string | undefined): Array<{
223
236
  name: string;