@playdrop/playdrop-cli 0.9.6 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/config/client-meta.json +1 -2
  2. package/dist/apiClient.d.ts +2 -0
  3. package/dist/apiClient.js +27 -2
  4. package/dist/appUrls.d.ts +1 -0
  5. package/dist/appUrls.js +9 -0
  6. package/dist/apps/build.js +39 -28
  7. package/dist/apps/index.d.ts +1 -0
  8. package/dist/apps/index.js +2 -0
  9. package/dist/apps/launchCheck.d.ts +2 -0
  10. package/dist/apps/launchCheck.js +31 -6
  11. package/dist/apps/registration.d.ts +1 -0
  12. package/dist/apps/registration.js +1 -0
  13. package/dist/apps/upload.d.ts +1 -0
  14. package/dist/apps/upload.js +4 -17
  15. package/dist/captureRuntime.d.ts +1 -0
  16. package/dist/captureRuntime.js +308 -0
  17. package/dist/catalogue.d.ts +4 -2
  18. package/dist/catalogue.js +50 -7
  19. package/dist/commandContext.js +42 -3
  20. package/dist/commands/capture.d.ts +1 -0
  21. package/dist/commands/capture.js +30 -13
  22. package/dist/commands/create.d.ts +0 -1
  23. package/dist/commands/create.js +2 -151
  24. package/dist/commands/creations.d.ts +0 -13
  25. package/dist/commands/creations.js +0 -141
  26. package/dist/commands/dev.d.ts +2 -1
  27. package/dist/commands/dev.js +23 -6
  28. package/dist/commands/devServer.js +3 -1
  29. package/dist/commands/generation.d.ts +1 -0
  30. package/dist/commands/generation.js +274 -0
  31. package/dist/commands/upload.d.ts +27 -1
  32. package/dist/commands/upload.js +962 -21
  33. package/dist/commands/validate.js +5 -0
  34. package/dist/commands/worker/runtime.d.ts +69 -0
  35. package/dist/commands/worker/runtime.js +414 -0
  36. package/dist/commands/worker.d.ts +144 -0
  37. package/dist/commands/worker.js +2219 -0
  38. package/dist/config.d.ts +2 -0
  39. package/dist/config.js +23 -0
  40. package/dist/index.js +71 -30
  41. package/dist/shellProbe.d.ts +1 -1
  42. package/dist/shellProbe.js +3 -3
  43. package/dist/workspaceAuth.d.ts +1 -0
  44. package/dist/workspaceAuth.js +8 -0
  45. package/node_modules/@playdrop/api-client/dist/client.d.ts +31 -14
  46. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  47. package/node_modules/@playdrop/api-client/dist/client.js +2 -2
  48. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +3 -1
  49. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  50. package/node_modules/@playdrop/api-client/dist/domains/admin.js +45 -0
  51. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +72 -0
  52. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -0
  53. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +442 -0
  54. package/node_modules/@playdrop/api-client/dist/index.d.ts +31 -14
  55. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  56. package/node_modules/@playdrop/api-client/dist/index.js +134 -38
  57. package/node_modules/@playdrop/config/client-meta.json +1 -2
  58. package/node_modules/@playdrop/types/dist/api.d.ts +501 -74
  59. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  60. package/node_modules/@playdrop/types/dist/api.js +90 -9
  61. package/node_modules/@playdrop/types/dist/app.d.ts +2 -0
  62. package/node_modules/@playdrop/types/dist/app.d.ts.map +1 -1
  63. package/node_modules/@playdrop/types/dist/app.js +3 -0
  64. package/node_modules/@playdrop/types/dist/version.d.ts +1 -0
  65. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  66. package/package.json +2 -1
  67. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts +0 -46
  68. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts.map +0 -1
  69. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.js +0 -177
@@ -10,6 +10,7 @@ const uploadLog_1 = require("../uploadLog");
10
10
  const apps_1 = require("../apps");
11
11
  const assetSpecs_1 = require("../assetSpecs");
12
12
  const externalAssetPackValidation_1 = require("../externalAssetPackValidation");
13
+ const dev_1 = require("./dev");
13
14
  const upload_content_1 = require("./upload-content");
14
15
  function buildLocalAssetSpecLookups(tasks) {
15
16
  const exactByRef = new Map();
@@ -114,6 +115,8 @@ async function loadHostedLaunchValidationContext(workspacePath, envOverride) {
114
115
  apiBase: ctx.envConfig.apiBase,
115
116
  webBase: ctx.envConfig.webBase ?? null,
116
117
  token: ctx.token,
118
+ devRouterPort: (0, dev_1.resolveDevRouterPort)(process.env.PLAYDROP_DEV_ROUTER_PORT),
119
+ taskScoped: Boolean(ctx.workspaceAuth?.config.taskToken),
117
120
  };
118
121
  }
119
122
  function printResolvedValidationAccount(ctx) {
@@ -250,6 +253,8 @@ async function validate(pathOrName, options = {}) {
250
253
  task,
251
254
  token: launchContext.token,
252
255
  currentUser: launchContext.currentUser,
256
+ devRouterPort: launchContext.devRouterPort,
257
+ allowUnregisteredViewerLaunch: launchContext.taskScoped,
253
258
  });
254
259
  if (launchCheck.status !== 'PASSED') {
255
260
  throw new Error((0, apps_1.formatHostedLaunchCheckFailure)(task.name, launchCheck, 'local'));
@@ -0,0 +1,69 @@
1
+ export declare const DEFAULT_CODEX_TIMEOUT_MS: number;
2
+ export declare const DEFAULT_WORKER_TOKEN_CAP = 20000000;
3
+ export declare const DEFAULT_CODEX_LOG_TAIL_CHARS = 12000;
4
+ export declare const DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS = 10000;
5
+ export type LoggedProcessResult = {
6
+ exitCode: number | null;
7
+ signal: NodeJS.Signals | null;
8
+ stdout: string;
9
+ stderr: string;
10
+ outputTail: string;
11
+ timedOut: boolean;
12
+ tokensUsed: number | null;
13
+ };
14
+ export type LoggedProcessTranscriptChunk = {
15
+ stream: 'stdout' | 'stderr';
16
+ content: string;
17
+ };
18
+ export declare function readPositiveEnvInt(name: string, fallback: number): number;
19
+ export declare function readEnvBoolean(name: string, fallback: boolean): boolean;
20
+ export declare function extractCodexTokensUsed(output: string): number | null;
21
+ export declare function assertWorkerTokenUsageWithinCap(input: {
22
+ tokensUsed: number | null;
23
+ tokenCap: number;
24
+ }): boolean;
25
+ export declare function runLoggedProcess(input: {
26
+ command: string;
27
+ args: string[];
28
+ cwd: string;
29
+ env: NodeJS.ProcessEnv;
30
+ stdin?: string;
31
+ timeoutMs: number;
32
+ maxOutputChars: number;
33
+ transcriptFlushIntervalMs?: number;
34
+ onTranscriptChunks?: (chunks: LoggedProcessTranscriptChunk[]) => Promise<void> | void;
35
+ onChild?: (controls: {
36
+ terminate: () => void;
37
+ }) => void;
38
+ }): Promise<LoggedProcessResult>;
39
+ export declare function buildCodexExecArgs(input: {
40
+ workspaceDir: string;
41
+ model: string;
42
+ reasoningEffort: string;
43
+ networkAccess: boolean;
44
+ sandboxMode?: CodexSandboxMode;
45
+ }): string[];
46
+ export type CodexSandboxMode = 'danger-full-access' | 'workspace-write' | 'read-only';
47
+ export declare function readCodexSandboxMode(env?: NodeJS.ProcessEnv): CodexSandboxMode;
48
+ export declare function buildClaudeExecArgs(input: {
49
+ model: string;
50
+ effort?: string | null;
51
+ workspaceDir?: string | null;
52
+ denyReadRoots?: string[] | null;
53
+ pluginDir?: string | null;
54
+ }): string[];
55
+ export declare function buildClaudePermissionSettings(denyReadRoots?: string[]): {
56
+ permissions: {
57
+ allow: string[];
58
+ deny: string[];
59
+ };
60
+ };
61
+ export declare function buildClaudeDeniedPermissionRules(denyReadRoots?: string[]): string[];
62
+ export declare function buildWorkerChildEnv(input: {
63
+ binDir: string;
64
+ taskId: number;
65
+ envName: string;
66
+ eventDir?: string;
67
+ devPort?: number;
68
+ baseEnv?: NodeJS.ProcessEnv;
69
+ }): NodeJS.ProcessEnv;
@@ -0,0 +1,414 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS = exports.DEFAULT_CODEX_LOG_TAIL_CHARS = exports.DEFAULT_WORKER_TOKEN_CAP = exports.DEFAULT_CODEX_TIMEOUT_MS = void 0;
7
+ exports.readPositiveEnvInt = readPositiveEnvInt;
8
+ exports.readEnvBoolean = readEnvBoolean;
9
+ exports.extractCodexTokensUsed = extractCodexTokensUsed;
10
+ exports.assertWorkerTokenUsageWithinCap = assertWorkerTokenUsageWithinCap;
11
+ exports.runLoggedProcess = runLoggedProcess;
12
+ exports.buildCodexExecArgs = buildCodexExecArgs;
13
+ exports.readCodexSandboxMode = readCodexSandboxMode;
14
+ exports.buildClaudeExecArgs = buildClaudeExecArgs;
15
+ exports.buildClaudePermissionSettings = buildClaudePermissionSettings;
16
+ exports.buildClaudeDeniedPermissionRules = buildClaudeDeniedPermissionRules;
17
+ exports.buildWorkerChildEnv = buildWorkerChildEnv;
18
+ const node_child_process_1 = require("node:child_process");
19
+ const node_path_1 = __importDefault(require("node:path"));
20
+ const CHILD_KILL_GRACE_MS = 10000;
21
+ exports.DEFAULT_CODEX_TIMEOUT_MS = 2 * 60 * 60 * 1000;
22
+ exports.DEFAULT_WORKER_TOKEN_CAP = 20000000;
23
+ exports.DEFAULT_CODEX_LOG_TAIL_CHARS = 12000;
24
+ exports.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS = 10000;
25
+ function tailText(value, maxChars) {
26
+ if (value.length <= maxChars) {
27
+ return value;
28
+ }
29
+ return value.slice(value.length - maxChars);
30
+ }
31
+ function readPositiveEnvInt(name, fallback) {
32
+ const raw = process.env[name]?.trim();
33
+ if (!raw) {
34
+ return fallback;
35
+ }
36
+ const parsed = Number.parseInt(raw, 10);
37
+ if (!Number.isInteger(parsed) || parsed <= 0) {
38
+ throw new Error(`invalid_${name.toLowerCase()}`);
39
+ }
40
+ return parsed;
41
+ }
42
+ function readEnvBoolean(name, fallback) {
43
+ const raw = process.env[name]?.trim().toLowerCase();
44
+ if (!raw) {
45
+ return fallback;
46
+ }
47
+ if (raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on') {
48
+ return true;
49
+ }
50
+ if (raw === '0' || raw === 'false' || raw === 'no' || raw === 'off') {
51
+ return false;
52
+ }
53
+ throw new Error(`invalid_${name.toLowerCase()}`);
54
+ }
55
+ function extractCodexTokensUsed(output) {
56
+ for (const line of output.split(/\r?\n/).reverse()) {
57
+ const trimmed = line.trim();
58
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
59
+ continue;
60
+ }
61
+ try {
62
+ const parsed = JSON.parse(trimmed);
63
+ const usage = parsed.usage;
64
+ if (!usage) {
65
+ continue;
66
+ }
67
+ const tokenKeys = [
68
+ 'input_tokens',
69
+ 'output_tokens',
70
+ 'cache_creation_input_tokens',
71
+ 'cache_read_input_tokens',
72
+ ];
73
+ let total = 0;
74
+ let sawTokenValue = false;
75
+ for (const key of tokenKeys) {
76
+ const value = usage[key];
77
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
78
+ continue;
79
+ }
80
+ total += value;
81
+ sawTokenValue = true;
82
+ }
83
+ if (sawTokenValue) {
84
+ return Math.ceil(total);
85
+ }
86
+ }
87
+ catch {
88
+ // Fall through to the plain-text patterns below.
89
+ }
90
+ }
91
+ const patterns = [
92
+ /total\s+tokens\s*:?\s*([\d,]+)/i,
93
+ /tokens\s+used\s*:?\s*([\d,]+)/i,
94
+ /"total_tokens"\s*:\s*([\d,]+)/i,
95
+ ];
96
+ for (const pattern of patterns) {
97
+ const match = pattern.exec(output);
98
+ if (!match?.[1]) {
99
+ continue;
100
+ }
101
+ const parsed = Number.parseInt(match[1].replace(/,/g, ''), 10);
102
+ if (Number.isInteger(parsed) && parsed >= 0) {
103
+ return parsed;
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ function assertWorkerTokenUsageWithinCap(input) {
109
+ if (input.tokensUsed === null) {
110
+ return false;
111
+ }
112
+ if (input.tokensUsed > input.tokenCap) {
113
+ throw new Error(`token_cap_exceeded:${input.tokensUsed}:${input.tokenCap}`);
114
+ }
115
+ return true;
116
+ }
117
+ async function runLoggedProcess(input) {
118
+ if (!input.command.trim()) {
119
+ throw new Error('invalid_process_command');
120
+ }
121
+ if (!Number.isInteger(input.timeoutMs) || input.timeoutMs <= 0) {
122
+ throw new Error('invalid_process_timeout');
123
+ }
124
+ if (!Number.isInteger(input.maxOutputChars) || input.maxOutputChars <= 0) {
125
+ throw new Error('invalid_process_log_tail');
126
+ }
127
+ const transcriptFlushIntervalMs = input.transcriptFlushIntervalMs ?? exports.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS;
128
+ if (input.onTranscriptChunks && (!Number.isInteger(transcriptFlushIntervalMs) || transcriptFlushIntervalMs <= 0)) {
129
+ throw new Error('invalid_process_transcript_flush_interval');
130
+ }
131
+ return await new Promise((resolve, reject) => {
132
+ let stdout = '';
133
+ let stderr = '';
134
+ let combined = '';
135
+ let timedOut = false;
136
+ let closed = false;
137
+ let killTimer = null;
138
+ let terminateKillTimer = null;
139
+ let transcriptFlushTimer = null;
140
+ let transcriptFlushError = null;
141
+ let transcriptFlushPromise = Promise.resolve();
142
+ const pendingTranscriptChunks = [];
143
+ const child = (0, node_child_process_1.spawn)(input.command, input.args, {
144
+ cwd: input.cwd,
145
+ stdio: ['pipe', 'pipe', 'pipe'],
146
+ env: input.env,
147
+ });
148
+ child.stdin?.end(input.stdin ?? '');
149
+ if (input.onChild) {
150
+ input.onChild({
151
+ terminate: () => {
152
+ if (closed) {
153
+ return;
154
+ }
155
+ child.kill('SIGTERM');
156
+ terminateKillTimer = setTimeout(() => {
157
+ if (!closed) {
158
+ child.kill('SIGKILL');
159
+ }
160
+ }, CHILD_KILL_GRACE_MS);
161
+ },
162
+ });
163
+ }
164
+ const flushTranscriptChunks = async () => {
165
+ if (!input.onTranscriptChunks || pendingTranscriptChunks.length <= 0) {
166
+ return;
167
+ }
168
+ const chunks = pendingTranscriptChunks.splice(0, pendingTranscriptChunks.length);
169
+ await input.onTranscriptChunks(chunks);
170
+ };
171
+ const queueTranscriptFlush = () => {
172
+ transcriptFlushPromise = transcriptFlushPromise.then(flushTranscriptChunks, flushTranscriptChunks);
173
+ return transcriptFlushPromise;
174
+ };
175
+ const handleTranscriptFlushError = (error) => {
176
+ transcriptFlushError = transcriptFlushError ?? error;
177
+ if (!closed) {
178
+ child.kill('SIGTERM');
179
+ }
180
+ };
181
+ if (input.onTranscriptChunks) {
182
+ transcriptFlushTimer = setInterval(() => {
183
+ queueTranscriptFlush().catch(handleTranscriptFlushError);
184
+ }, transcriptFlushIntervalMs);
185
+ }
186
+ const append = (stream, chunk) => {
187
+ const text = chunk.toString('utf8');
188
+ if (stream === 'stdout') {
189
+ process.stdout.write(text);
190
+ stdout = tailText(stdout + text, input.maxOutputChars);
191
+ }
192
+ else {
193
+ process.stderr.write(text);
194
+ stderr = tailText(stderr + text, input.maxOutputChars);
195
+ }
196
+ if (input.onTranscriptChunks) {
197
+ pendingTranscriptChunks.push({ stream, content: text });
198
+ }
199
+ combined = tailText(combined + text, input.maxOutputChars);
200
+ };
201
+ const timeout = setTimeout(() => {
202
+ timedOut = true;
203
+ child.kill('SIGTERM');
204
+ killTimer = setTimeout(() => {
205
+ if (!closed) {
206
+ child.kill('SIGKILL');
207
+ }
208
+ }, 5000);
209
+ }, input.timeoutMs);
210
+ child.stdout?.on('data', (chunk) => append('stdout', chunk));
211
+ child.stderr?.on('data', (chunk) => append('stderr', chunk));
212
+ child.on('error', reject);
213
+ child.on('close', (code, signal) => {
214
+ closed = true;
215
+ clearTimeout(timeout);
216
+ if (killTimer) {
217
+ clearTimeout(killTimer);
218
+ }
219
+ if (terminateKillTimer) {
220
+ clearTimeout(terminateKillTimer);
221
+ }
222
+ if (transcriptFlushTimer) {
223
+ clearInterval(transcriptFlushTimer);
224
+ }
225
+ void (async () => {
226
+ await queueTranscriptFlush();
227
+ if (transcriptFlushError) {
228
+ throw transcriptFlushError;
229
+ }
230
+ const outputTail = tailText(combined, input.maxOutputChars);
231
+ resolve({
232
+ exitCode: code,
233
+ signal,
234
+ stdout,
235
+ stderr,
236
+ outputTail,
237
+ timedOut,
238
+ tokensUsed: extractCodexTokensUsed(`${stdout}\n${stderr}\n${outputTail}`),
239
+ });
240
+ })().catch(reject);
241
+ });
242
+ });
243
+ }
244
+ function buildCodexExecArgs(input) {
245
+ const sandboxMode = input.sandboxMode ?? 'danger-full-access';
246
+ const args = [
247
+ 'exec',
248
+ '--sandbox',
249
+ sandboxMode,
250
+ '--cd',
251
+ input.workspaceDir,
252
+ '--skip-git-repo-check',
253
+ '--ephemeral',
254
+ '--model',
255
+ input.model,
256
+ '-c',
257
+ `model_reasoning_effort="${input.reasoningEffort}"`,
258
+ '-',
259
+ ];
260
+ if (sandboxMode === 'workspace-write') {
261
+ args.splice(args.length - 3, 0, '-c', `sandbox_workspace_write.network_access=${input.networkAccess ? 'true' : 'false'}`);
262
+ }
263
+ return args;
264
+ }
265
+ function readCodexSandboxMode(env = process.env) {
266
+ const raw = env.PLAYDROP_WORKER_CODEX_SANDBOX?.trim();
267
+ if (!raw) {
268
+ return 'danger-full-access';
269
+ }
270
+ if (raw === 'danger-full-access' || raw === 'workspace-write' || raw === 'read-only') {
271
+ return raw;
272
+ }
273
+ throw new Error(`invalid_playdrop_worker_codex_sandbox:${raw}`);
274
+ }
275
+ function buildClaudeExecArgs(input) {
276
+ const model = input.model.trim();
277
+ if (!model) {
278
+ throw new Error('agent_task_assignment_model_missing');
279
+ }
280
+ const permissionSettings = buildClaudePermissionSettings(input.denyReadRoots ?? []);
281
+ const tools = 'Bash,Edit,Write,Read,Glob,Grep';
282
+ const args = [
283
+ '-p',
284
+ '--model',
285
+ model,
286
+ '--output-format',
287
+ 'stream-json',
288
+ '--verbose',
289
+ '--include-partial-messages',
290
+ '--no-session-persistence',
291
+ '--permission-mode',
292
+ 'acceptEdits',
293
+ '--tools',
294
+ tools,
295
+ '--allowedTools',
296
+ ...CLAUDE_ALLOWED_PERMISSION_RULES,
297
+ '--disallowedTools',
298
+ ...buildClaudeDeniedPermissionRules(input.denyReadRoots ?? []),
299
+ '--settings',
300
+ JSON.stringify(permissionSettings),
301
+ ];
302
+ const pluginDir = typeof input.pluginDir === 'string' ? input.pluginDir.trim() : '';
303
+ if (pluginDir) {
304
+ args.push('--plugin-dir', pluginDir);
305
+ }
306
+ const effort = typeof input.effort === 'string' ? input.effort.trim() : '';
307
+ if (effort) {
308
+ args.push('--effort', effort);
309
+ }
310
+ return args;
311
+ }
312
+ const CLAUDE_ALLOWED_PERMISSION_RULES = [
313
+ 'Read',
314
+ 'Edit',
315
+ 'Write',
316
+ 'Glob',
317
+ 'Grep',
318
+ 'Bash',
319
+ 'Bash(playdrop *)',
320
+ 'Bash(./bin/playdrop *)',
321
+ 'Bash(bin/playdrop *)',
322
+ 'Bash(/Users/*/PlayDropWorker/tasks/*/bin/playdrop *)',
323
+ 'Bash(node *)',
324
+ 'Bash(npm *)',
325
+ 'Bash(mkdir *)',
326
+ 'Bash(cp *)',
327
+ 'Bash(mv *)',
328
+ 'Bash(file *)',
329
+ 'Bash(sips *)',
330
+ 'Bash(ls *)',
331
+ 'Bash(pwd)',
332
+ 'Bash(cat *)',
333
+ 'Bash(head *)',
334
+ 'Bash(tail *)',
335
+ 'Bash(rg *)',
336
+ 'Bash(grep *)',
337
+ 'Bash(find *)',
338
+ 'Bash(which *)',
339
+ 'Bash(wc *)',
340
+ 'Bash(stat *)',
341
+ 'Bash(du *)',
342
+ ];
343
+ function normalizeClaudePermissionPath(value) {
344
+ const trimmed = value.trim();
345
+ if (!trimmed) {
346
+ return null;
347
+ }
348
+ return node_path_1.default.resolve(trimmed).replace(/\\/g, '/').replace(/\/+$/, '');
349
+ }
350
+ function buildClaudePermissionSettings(denyReadRoots = []) {
351
+ return {
352
+ permissions: {
353
+ allow: [...CLAUDE_ALLOWED_PERMISSION_RULES],
354
+ deny: buildClaudeDeniedPermissionRules(denyReadRoots),
355
+ },
356
+ };
357
+ }
358
+ function buildClaudeDeniedPermissionRules(denyReadRoots = []) {
359
+ const deny = new Set([
360
+ 'Bash(rm *)',
361
+ 'Bash(git *)',
362
+ ]);
363
+ for (const rawRoot of denyReadRoots) {
364
+ const root = normalizeClaudePermissionPath(rawRoot);
365
+ if (!root) {
366
+ continue;
367
+ }
368
+ deny.add(`Read(//${root.replace(/^\/+/, '')}/**)`);
369
+ deny.add(`Bash(* ${root}*)`);
370
+ }
371
+ return [...deny];
372
+ }
373
+ function buildWorkerChildEnv(input) {
374
+ const base = input.baseEnv ?? process.env;
375
+ const child = {};
376
+ for (const key of ['HOME', 'USER', 'SHELL', 'TMPDIR', 'LANG', 'TERM']) {
377
+ const value = base[key];
378
+ if (typeof value === 'string') {
379
+ child[key] = value;
380
+ }
381
+ }
382
+ for (const [key, value] of Object.entries(base)) {
383
+ if (key.startsWith('LC_') && typeof value === 'string') {
384
+ child[key] = value;
385
+ }
386
+ }
387
+ child.PATH = typeof base.PATH === 'string' && base.PATH
388
+ ? `${input.binDir}${node_path_1.default.delimiter}${base.PATH}`
389
+ : input.binDir;
390
+ for (const key of [
391
+ 'PLAYDROP_API_BASE',
392
+ `PLAYDROP_API_BASE_${input.envName.toUpperCase()}`,
393
+ 'PLAYDROP_AI_BASE',
394
+ `PLAYDROP_AI_BASE_${input.envName.toUpperCase()}`,
395
+ ]) {
396
+ const value = base[key];
397
+ if (typeof value === 'string') {
398
+ child[key] = value;
399
+ }
400
+ }
401
+ child.PLAYDROP_WORKER_CONTEXT = '1';
402
+ child.PLAYDROP_WORKER_TASK_ID = String(input.taskId);
403
+ if (input.devPort !== undefined) {
404
+ if (!Number.isInteger(input.devPort) || input.devPort <= 0 || input.devPort > 65535) {
405
+ throw new Error('invalid_worker_task_dev_port');
406
+ }
407
+ child.PLAYDROP_WORKER_TASK_DEV_PORT = String(input.devPort);
408
+ child.PLAYDROP_DEV_ROUTER_PORT = String(input.devPort);
409
+ }
410
+ if (input.eventDir?.trim()) {
411
+ child.PLAYDROP_WORKER_EVENT_DIR = input.eventDir.trim();
412
+ }
413
+ return child;
414
+ }
@@ -0,0 +1,144 @@
1
+ import type { ApiClient } from '@playdrop/api-client';
2
+ import { type AgentExecutionTarget, type AgentRuntime, type AgentTaskResponse, type AgentWorkerCapabilities, type WorkerAgentTaskAssignmentResponse, type WorkerAgentTaskBaseSourceResponse, type WorkerAgentTaskWorkspaceFileResponse, type WorkerClaimAgentTaskResponse } from '@playdrop/types';
3
+ import { type WorkerPlaydropAssetRequirement } from './upload';
4
+ import { type LoggedProcessResult, type LoggedProcessTranscriptChunk } from './worker/runtime';
5
+ export { DEFAULT_CODEX_TIMEOUT_MS, DEFAULT_WORKER_TOKEN_CAP, assertWorkerTokenUsageWithinCap, buildCodexExecArgs, buildClaudeExecArgs, buildClaudePermissionSettings, buildWorkerChildEnv, extractCodexTokensUsed, readEnvBoolean, readCodexSandboxMode, runLoggedProcess, } from './worker/runtime';
6
+ export type { LoggedProcessResult, LoggedProcessTranscriptChunk, } from './worker/runtime';
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, project validation/build/dev/capture, read-only catalogue/documentation lookup, and PlayDrop AI generation are permitted.";
9
+ type WorkerStartOptions = {
10
+ env?: string;
11
+ once?: boolean;
12
+ name?: string;
13
+ };
14
+ type TaskReportOptions = {
15
+ env?: string;
16
+ phase?: string;
17
+ pct?: string | number;
18
+ message?: string;
19
+ kind?: string;
20
+ };
21
+ type TaskCatalogueReportOptions = {
22
+ env?: string;
23
+ file?: string;
24
+ message?: string;
25
+ };
26
+ type TaskCompleteOptions = {
27
+ env?: string;
28
+ };
29
+ type TaskUploadOptions = {
30
+ env?: string;
31
+ };
32
+ type TaskFailOptions = {
33
+ env?: string;
34
+ message?: string;
35
+ };
36
+ export type WorkerHealthAlertInput = {
37
+ state: 'started' | 'stopped' | 'crashed';
38
+ env: string;
39
+ workerName: string;
40
+ taskId?: number | null;
41
+ detail?: string | null;
42
+ };
43
+ type WorkerTaskState = {
44
+ taskId: number;
45
+ env: string;
46
+ target: AgentExecutionTarget;
47
+ leaseExpiresAt: string | null;
48
+ claimedAt: string;
49
+ };
50
+ export declare function allocateWorkerDevPort(input: {
51
+ activePorts: ReadonlySet<number>;
52
+ basePort?: number;
53
+ maxParallelTasks?: number;
54
+ }): number;
55
+ export declare function isWorkerContextCommandAllowed(argv: string[]): boolean;
56
+ export declare function resolveWorkerExecutionTargetFromRole(role: unknown): AgentExecutionTarget;
57
+ export declare function resolveWorkerClaimTaskAssignment(claim: WorkerClaimAgentTaskResponse): WorkerAgentTaskAssignmentResponse | null;
58
+ export declare function nextClaimBackoffMs(currentMs: number): number;
59
+ export declare function claimBackoffDelayMs(currentMs: number, random?: () => number): number;
60
+ export declare function resolveWorkerHomeDir(): string;
61
+ export declare function hasAgentTaskUploadedArtifact(task: Pick<AgentTaskResponse, 'appId' | 'appVersionId'>): boolean;
62
+ export declare function appendWorkerTranscriptChunks(input: {
63
+ client: Pick<ApiClient, 'workerAppendAgentTaskTranscriptChunks'>;
64
+ taskId: number;
65
+ workerKey: string;
66
+ leaseToken: string;
67
+ chunks: LoggedProcessTranscriptChunk[];
68
+ batchSize?: number;
69
+ }): Promise<void>;
70
+ export declare function assertTaskUploadResultMatchesContext(input: {
71
+ taskContext: {
72
+ taskId: number;
73
+ creatorUsername: string;
74
+ outputAppName?: string | null;
75
+ outputVersion: string;
76
+ };
77
+ uploadResult: {
78
+ taskId: number;
79
+ appName: string;
80
+ version: string;
81
+ creatorUsername: string;
82
+ };
83
+ }): void;
84
+ export declare function stageAssignmentWorkspaceFiles(workspaceDir: string, files: readonly WorkerAgentTaskWorkspaceFileResponse[]): Promise<void>;
85
+ export declare function resolvePlaydropPluginRoot(input?: {
86
+ homeDir?: string;
87
+ env?: NodeJS.ProcessEnv;
88
+ }): string;
89
+ export declare function stagePlaydropPluginReferences(input: {
90
+ workspaceDir: string;
91
+ pluginRoot: string;
92
+ }): Promise<string[]>;
93
+ export declare function discoverWorkerProjectRoot(workspaceDir: string): string;
94
+ export declare function readWorkerTaskState(): WorkerTaskState | null;
95
+ export declare function buildWorkerHealthAlertText(input: WorkerHealthAlertInput): string;
96
+ export declare function isWorkerAuthFailureError(error: unknown): boolean;
97
+ export declare function classifyWorkerEventDrainError(error: unknown): 'session_expired' | 'lease_invalid' | null;
98
+ export declare function assertWorkerClaimErrorRetryable(error: unknown): void;
99
+ export declare function resolvePlaydropAssetRequirementFromText(value: unknown): WorkerPlaydropAssetRequirement | null;
100
+ export declare function loadWorkerEnvFile(envName: string | undefined, cwd?: string): string | null;
101
+ export declare function buildWorkerCapabilities(input: string | {
102
+ codexVersion?: string | null;
103
+ codexAuthenticated?: boolean | null;
104
+ claudeVersion?: string | null;
105
+ claudeAuthenticated?: boolean | null;
106
+ npmVersion?: string | null;
107
+ playwright?: {
108
+ version?: string;
109
+ chromiumInstalled?: boolean;
110
+ } | null;
111
+ maxParallelTasks?: number | null;
112
+ runningTaskCount?: number | null;
113
+ }): AgentWorkerCapabilities;
114
+ export declare function buildWorkerClaimBody(input: {
115
+ workerKey: string;
116
+ capabilities: AgentWorkerCapabilities;
117
+ runningTaskCount?: number;
118
+ }): {
119
+ workerKey: string;
120
+ capabilities: AgentWorkerCapabilities;
121
+ };
122
+ export type WorkerBaseApp = {
123
+ appName: string;
124
+ version: string;
125
+ appId: number;
126
+ appVersionId: number;
127
+ };
128
+ export declare function resolveWorkerBaseSource(value: WorkerAgentTaskBaseSourceResponse | null | undefined): WorkerBaseApp;
129
+ export declare function extractZipArchive(zipBuffer: Uint8Array, targetDir: string): string[];
130
+ export declare function readWorkerProjectVersion(projectDir: string, appName: string): string;
131
+ export declare function buildCataloguePreviewPayload(cataloguePath: string, workspaceDir?: string): Record<string, unknown>;
132
+ export declare function assertWorkerProjectVersionBumped(input: {
133
+ baseVersion: string;
134
+ projectVersion: string;
135
+ appName: string;
136
+ }): void;
137
+ export declare function buildAgentFailureCode(agent: AgentRuntime, result: LoggedProcessResult): string;
138
+ export declare function describeAgentFailureForEvent(errorCode: string): string;
139
+ export declare function startWorker(options?: WorkerStartOptions): Promise<void>;
140
+ export declare function reportTask(options: TaskReportOptions): Promise<void>;
141
+ export declare function reportCatalogueTask(options: TaskCatalogueReportOptions): Promise<void>;
142
+ export declare function uploadTask(options?: TaskUploadOptions): Promise<void>;
143
+ export declare function completeTask(options: TaskCompleteOptions): Promise<void>;
144
+ export declare function failTask(options: TaskFailOptions): Promise<void>;