@playdrop/playdrop-cli 0.9.6 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/client-meta.json +2 -2
- package/dist/apiClient.d.ts +10 -0
- package/dist/apiClient.js +55 -2
- package/dist/appUrls.d.ts +1 -0
- package/dist/appUrls.js +9 -0
- package/dist/apps/build.js +39 -28
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.js +2 -0
- package/dist/apps/launchCheck.d.ts +2 -0
- package/dist/apps/launchCheck.js +31 -6
- package/dist/apps/registration.d.ts +1 -0
- package/dist/apps/registration.js +1 -0
- package/dist/apps/upload.d.ts +1 -0
- package/dist/apps/upload.js +4 -17
- package/dist/captureRuntime.d.ts +14 -0
- package/dist/captureRuntime.js +329 -0
- package/dist/catalogue.d.ts +4 -2
- package/dist/catalogue.js +50 -7
- package/dist/commandContext.js +61 -4
- package/dist/commands/capture.d.ts +1 -0
- package/dist/commands/capture.js +30 -13
- package/dist/commands/captureRemote.d.ts +2 -0
- package/dist/commands/captureRemote.js +90 -0
- package/dist/commands/create.d.ts +0 -1
- package/dist/commands/create.js +2 -151
- package/dist/commands/creations.d.ts +0 -13
- package/dist/commands/creations.js +0 -141
- package/dist/commands/dev.d.ts +2 -1
- package/dist/commands/dev.js +23 -6
- package/dist/commands/devServer.js +3 -1
- package/dist/commands/generation.d.ts +1 -0
- package/dist/commands/generation.js +274 -0
- package/dist/commands/review.d.ts +46 -0
- package/dist/commands/review.js +353 -0
- package/dist/commands/upload.d.ts +27 -1
- package/dist/commands/upload.js +962 -21
- package/dist/commands/validate.js +5 -0
- package/dist/commands/worker/runtime.d.ts +81 -0
- package/dist/commands/worker/runtime.js +458 -0
- package/dist/commands/worker.d.ts +158 -0
- package/dist/commands/worker.js +2626 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +23 -0
- package/dist/index.js +116 -30
- package/dist/shellProbe.d.ts +1 -1
- package/dist/shellProbe.js +3 -3
- package/dist/workspaceAuth.d.ts +3 -0
- package/dist/workspaceAuth.js +14 -0
- package/node_modules/@playdrop/api-client/dist/client.d.ts +36 -15
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +2 -2
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +5 -2
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.js +51 -3
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +75 -0
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -0
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +478 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +36 -15
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +153 -42
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/types/dist/api.d.ts +662 -75
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +100 -9
- package/node_modules/@playdrop/types/dist/app.d.ts +2 -0
- package/node_modules/@playdrop/types/dist/app.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/app.js +3 -0
- package/node_modules/@playdrop/types/dist/version.d.ts +1 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/package.json +2 -1
- package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts +0 -46
- package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts.map +0 -1
- 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,81 @@
|
|
|
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
|
+
tokenUsage: AgentTokenUsage;
|
|
13
|
+
tokensUsed: number | null;
|
|
14
|
+
};
|
|
15
|
+
export type AgentTokenUsage = {
|
|
16
|
+
inputTokens: number | null;
|
|
17
|
+
outputTokens: number | null;
|
|
18
|
+
cacheCreationInputTokens: number | null;
|
|
19
|
+
cacheReadInputTokens: number | null;
|
|
20
|
+
totalTokens: number | null;
|
|
21
|
+
rawProviderUsage: Record<string, unknown> | null;
|
|
22
|
+
usageParseError: string | null;
|
|
23
|
+
};
|
|
24
|
+
export type LoggedProcessTranscriptChunk = {
|
|
25
|
+
stream: 'stdout' | 'stderr';
|
|
26
|
+
content: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function readPositiveEnvInt(name: string, fallback: number): number;
|
|
29
|
+
export declare function readEnvBoolean(name: string, fallback: boolean): boolean;
|
|
30
|
+
export declare function extractAgentTokenUsage(output: string): AgentTokenUsage;
|
|
31
|
+
export declare function extractCodexTokensUsed(output: string): number | null;
|
|
32
|
+
export declare function assertWorkerTokenUsageWithinCap(input: {
|
|
33
|
+
tokensUsed: number | null;
|
|
34
|
+
tokenCap: number;
|
|
35
|
+
}): boolean;
|
|
36
|
+
export declare function runLoggedProcess(input: {
|
|
37
|
+
command: string;
|
|
38
|
+
args: string[];
|
|
39
|
+
cwd: string;
|
|
40
|
+
env: NodeJS.ProcessEnv;
|
|
41
|
+
stdin?: string;
|
|
42
|
+
timeoutMs: number;
|
|
43
|
+
maxOutputChars: number;
|
|
44
|
+
transcriptFlushIntervalMs?: number;
|
|
45
|
+
onTranscriptChunks?: (chunks: LoggedProcessTranscriptChunk[]) => Promise<void> | void;
|
|
46
|
+
onChild?: (controls: {
|
|
47
|
+
terminate: () => void;
|
|
48
|
+
}) => void;
|
|
49
|
+
}): Promise<LoggedProcessResult>;
|
|
50
|
+
export declare function buildCodexExecArgs(input: {
|
|
51
|
+
workspaceDir: string;
|
|
52
|
+
model: string;
|
|
53
|
+
reasoningEffort: string;
|
|
54
|
+
networkAccess: boolean;
|
|
55
|
+
sandboxMode?: CodexSandboxMode;
|
|
56
|
+
}): string[];
|
|
57
|
+
export type CodexSandboxMode = 'danger-full-access' | 'workspace-write' | 'read-only';
|
|
58
|
+
export declare function readCodexSandboxMode(env?: NodeJS.ProcessEnv): CodexSandboxMode;
|
|
59
|
+
export declare function buildClaudeExecArgs(input: {
|
|
60
|
+
model: string;
|
|
61
|
+
effort?: string | null;
|
|
62
|
+
workspaceDir?: string | null;
|
|
63
|
+
denyReadRoots?: string[] | null;
|
|
64
|
+
pluginDir?: string | null;
|
|
65
|
+
}): string[];
|
|
66
|
+
export declare function buildClaudePermissionSettings(denyReadRoots?: string[]): {
|
|
67
|
+
permissions: {
|
|
68
|
+
allow: string[];
|
|
69
|
+
deny: string[];
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
export declare function buildClaudeDeniedPermissionRules(denyReadRoots?: string[]): string[];
|
|
73
|
+
export declare function buildWorkerChildEnv(input: {
|
|
74
|
+
binDir: string;
|
|
75
|
+
taskId: number;
|
|
76
|
+
attempt: number;
|
|
77
|
+
envName: string;
|
|
78
|
+
eventDir?: string;
|
|
79
|
+
devPort?: number;
|
|
80
|
+
baseEnv?: NodeJS.ProcessEnv;
|
|
81
|
+
}): NodeJS.ProcessEnv;
|
|
@@ -0,0 +1,458 @@
|
|
|
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.extractAgentTokenUsage = extractAgentTokenUsage;
|
|
10
|
+
exports.extractCodexTokensUsed = extractCodexTokensUsed;
|
|
11
|
+
exports.assertWorkerTokenUsageWithinCap = assertWorkerTokenUsageWithinCap;
|
|
12
|
+
exports.runLoggedProcess = runLoggedProcess;
|
|
13
|
+
exports.buildCodexExecArgs = buildCodexExecArgs;
|
|
14
|
+
exports.readCodexSandboxMode = readCodexSandboxMode;
|
|
15
|
+
exports.buildClaudeExecArgs = buildClaudeExecArgs;
|
|
16
|
+
exports.buildClaudePermissionSettings = buildClaudePermissionSettings;
|
|
17
|
+
exports.buildClaudeDeniedPermissionRules = buildClaudeDeniedPermissionRules;
|
|
18
|
+
exports.buildWorkerChildEnv = buildWorkerChildEnv;
|
|
19
|
+
const node_child_process_1 = require("node:child_process");
|
|
20
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
21
|
+
const CHILD_KILL_GRACE_MS = 10000;
|
|
22
|
+
exports.DEFAULT_CODEX_TIMEOUT_MS = 2 * 60 * 60 * 1000;
|
|
23
|
+
exports.DEFAULT_WORKER_TOKEN_CAP = 20000000;
|
|
24
|
+
exports.DEFAULT_CODEX_LOG_TAIL_CHARS = 12000;
|
|
25
|
+
exports.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS = 10000;
|
|
26
|
+
function tailText(value, maxChars) {
|
|
27
|
+
if (value.length <= maxChars) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
return value.slice(value.length - maxChars);
|
|
31
|
+
}
|
|
32
|
+
function readPositiveEnvInt(name, fallback) {
|
|
33
|
+
const raw = process.env[name]?.trim();
|
|
34
|
+
if (!raw) {
|
|
35
|
+
return fallback;
|
|
36
|
+
}
|
|
37
|
+
const parsed = Number.parseInt(raw, 10);
|
|
38
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
39
|
+
throw new Error(`invalid_${name.toLowerCase()}`);
|
|
40
|
+
}
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
function readEnvBoolean(name, fallback) {
|
|
44
|
+
const raw = process.env[name]?.trim().toLowerCase();
|
|
45
|
+
if (!raw) {
|
|
46
|
+
return fallback;
|
|
47
|
+
}
|
|
48
|
+
if (raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on') {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (raw === '0' || raw === 'false' || raw === 'no' || raw === 'off') {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`invalid_${name.toLowerCase()}`);
|
|
55
|
+
}
|
|
56
|
+
function normalizeTokenCount(value) {
|
|
57
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return Math.ceil(value);
|
|
61
|
+
}
|
|
62
|
+
function coerceRecord(value) {
|
|
63
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
function readStructuredUsage(parsed) {
|
|
69
|
+
const root = coerceRecord(parsed);
|
|
70
|
+
if (!root) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const directUsage = coerceRecord(root.usage);
|
|
74
|
+
if (directUsage) {
|
|
75
|
+
return directUsage;
|
|
76
|
+
}
|
|
77
|
+
const message = coerceRecord(root.message);
|
|
78
|
+
const messageUsage = coerceRecord(message?.usage);
|
|
79
|
+
if (messageUsage) {
|
|
80
|
+
return messageUsage;
|
|
81
|
+
}
|
|
82
|
+
const event = coerceRecord(root.event);
|
|
83
|
+
const eventUsage = coerceRecord(event?.usage);
|
|
84
|
+
if (eventUsage) {
|
|
85
|
+
return eventUsage;
|
|
86
|
+
}
|
|
87
|
+
const eventMessage = coerceRecord(event?.message);
|
|
88
|
+
const eventMessageUsage = coerceRecord(eventMessage?.usage);
|
|
89
|
+
if (eventMessageUsage) {
|
|
90
|
+
return eventMessageUsage;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
function extractAgentTokenUsage(output) {
|
|
95
|
+
for (const line of output.split(/\r?\n/).reverse()) {
|
|
96
|
+
const trimmed = line.trim();
|
|
97
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(trimmed);
|
|
102
|
+
const usage = readStructuredUsage(parsed);
|
|
103
|
+
if (!usage) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
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
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
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
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function extractCodexTokensUsed(output) {
|
|
144
|
+
return extractAgentTokenUsage(output).totalTokens;
|
|
145
|
+
}
|
|
146
|
+
function assertWorkerTokenUsageWithinCap(input) {
|
|
147
|
+
if (input.tokensUsed === null) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
if (input.tokensUsed > input.tokenCap) {
|
|
151
|
+
throw new Error(`token_cap_exceeded:${input.tokensUsed}:${input.tokenCap}`);
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
async function runLoggedProcess(input) {
|
|
156
|
+
if (!input.command.trim()) {
|
|
157
|
+
throw new Error('invalid_process_command');
|
|
158
|
+
}
|
|
159
|
+
if (!Number.isInteger(input.timeoutMs) || input.timeoutMs <= 0) {
|
|
160
|
+
throw new Error('invalid_process_timeout');
|
|
161
|
+
}
|
|
162
|
+
if (!Number.isInteger(input.maxOutputChars) || input.maxOutputChars <= 0) {
|
|
163
|
+
throw new Error('invalid_process_log_tail');
|
|
164
|
+
}
|
|
165
|
+
const transcriptFlushIntervalMs = input.transcriptFlushIntervalMs ?? exports.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS;
|
|
166
|
+
if (input.onTranscriptChunks && (!Number.isInteger(transcriptFlushIntervalMs) || transcriptFlushIntervalMs <= 0)) {
|
|
167
|
+
throw new Error('invalid_process_transcript_flush_interval');
|
|
168
|
+
}
|
|
169
|
+
return await new Promise((resolve, reject) => {
|
|
170
|
+
let stdout = '';
|
|
171
|
+
let stderr = '';
|
|
172
|
+
let combined = '';
|
|
173
|
+
let timedOut = false;
|
|
174
|
+
let closed = false;
|
|
175
|
+
let killTimer = null;
|
|
176
|
+
let terminateKillTimer = null;
|
|
177
|
+
let transcriptFlushTimer = null;
|
|
178
|
+
let transcriptFlushError = null;
|
|
179
|
+
let transcriptFlushPromise = Promise.resolve();
|
|
180
|
+
const pendingTranscriptChunks = [];
|
|
181
|
+
const child = (0, node_child_process_1.spawn)(input.command, input.args, {
|
|
182
|
+
cwd: input.cwd,
|
|
183
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
184
|
+
env: input.env,
|
|
185
|
+
});
|
|
186
|
+
child.stdin?.end(input.stdin ?? '');
|
|
187
|
+
if (input.onChild) {
|
|
188
|
+
input.onChild({
|
|
189
|
+
terminate: () => {
|
|
190
|
+
if (closed) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
child.kill('SIGTERM');
|
|
194
|
+
terminateKillTimer = setTimeout(() => {
|
|
195
|
+
if (!closed) {
|
|
196
|
+
child.kill('SIGKILL');
|
|
197
|
+
}
|
|
198
|
+
}, CHILD_KILL_GRACE_MS);
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const flushTranscriptChunks = async () => {
|
|
203
|
+
if (!input.onTranscriptChunks || pendingTranscriptChunks.length <= 0) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const chunks = pendingTranscriptChunks.splice(0, pendingTranscriptChunks.length);
|
|
207
|
+
await input.onTranscriptChunks(chunks);
|
|
208
|
+
};
|
|
209
|
+
const queueTranscriptFlush = () => {
|
|
210
|
+
transcriptFlushPromise = transcriptFlushPromise.then(flushTranscriptChunks, flushTranscriptChunks);
|
|
211
|
+
return transcriptFlushPromise;
|
|
212
|
+
};
|
|
213
|
+
const handleTranscriptFlushError = (error) => {
|
|
214
|
+
transcriptFlushError = transcriptFlushError ?? error;
|
|
215
|
+
if (!closed) {
|
|
216
|
+
child.kill('SIGTERM');
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
if (input.onTranscriptChunks) {
|
|
220
|
+
transcriptFlushTimer = setInterval(() => {
|
|
221
|
+
queueTranscriptFlush().catch(handleTranscriptFlushError);
|
|
222
|
+
}, transcriptFlushIntervalMs);
|
|
223
|
+
}
|
|
224
|
+
const append = (stream, chunk) => {
|
|
225
|
+
const text = chunk.toString('utf8');
|
|
226
|
+
if (stream === 'stdout') {
|
|
227
|
+
process.stdout.write(text);
|
|
228
|
+
stdout = tailText(stdout + text, input.maxOutputChars);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
process.stderr.write(text);
|
|
232
|
+
stderr = tailText(stderr + text, input.maxOutputChars);
|
|
233
|
+
}
|
|
234
|
+
if (input.onTranscriptChunks) {
|
|
235
|
+
pendingTranscriptChunks.push({ stream, content: text });
|
|
236
|
+
}
|
|
237
|
+
combined = tailText(combined + text, input.maxOutputChars);
|
|
238
|
+
};
|
|
239
|
+
const timeout = setTimeout(() => {
|
|
240
|
+
timedOut = true;
|
|
241
|
+
child.kill('SIGTERM');
|
|
242
|
+
killTimer = setTimeout(() => {
|
|
243
|
+
if (!closed) {
|
|
244
|
+
child.kill('SIGKILL');
|
|
245
|
+
}
|
|
246
|
+
}, 5000);
|
|
247
|
+
}, input.timeoutMs);
|
|
248
|
+
child.stdout?.on('data', (chunk) => append('stdout', chunk));
|
|
249
|
+
child.stderr?.on('data', (chunk) => append('stderr', chunk));
|
|
250
|
+
child.on('error', reject);
|
|
251
|
+
child.on('close', (code, signal) => {
|
|
252
|
+
closed = true;
|
|
253
|
+
clearTimeout(timeout);
|
|
254
|
+
if (killTimer) {
|
|
255
|
+
clearTimeout(killTimer);
|
|
256
|
+
}
|
|
257
|
+
if (terminateKillTimer) {
|
|
258
|
+
clearTimeout(terminateKillTimer);
|
|
259
|
+
}
|
|
260
|
+
if (transcriptFlushTimer) {
|
|
261
|
+
clearInterval(transcriptFlushTimer);
|
|
262
|
+
}
|
|
263
|
+
void (async () => {
|
|
264
|
+
await queueTranscriptFlush();
|
|
265
|
+
if (transcriptFlushError) {
|
|
266
|
+
throw transcriptFlushError;
|
|
267
|
+
}
|
|
268
|
+
const outputTail = tailText(combined, input.maxOutputChars);
|
|
269
|
+
const tokenUsage = extractAgentTokenUsage(`${stdout}\n${stderr}\n${outputTail}`);
|
|
270
|
+
resolve({
|
|
271
|
+
exitCode: code,
|
|
272
|
+
signal,
|
|
273
|
+
stdout,
|
|
274
|
+
stderr,
|
|
275
|
+
outputTail,
|
|
276
|
+
timedOut,
|
|
277
|
+
tokenUsage,
|
|
278
|
+
tokensUsed: tokenUsage.totalTokens,
|
|
279
|
+
});
|
|
280
|
+
})().catch(reject);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
function buildCodexExecArgs(input) {
|
|
285
|
+
const sandboxMode = input.sandboxMode ?? 'danger-full-access';
|
|
286
|
+
const args = [
|
|
287
|
+
'exec',
|
|
288
|
+
'--sandbox',
|
|
289
|
+
sandboxMode,
|
|
290
|
+
'--cd',
|
|
291
|
+
input.workspaceDir,
|
|
292
|
+
'--skip-git-repo-check',
|
|
293
|
+
'--ephemeral',
|
|
294
|
+
'--model',
|
|
295
|
+
input.model,
|
|
296
|
+
'-c',
|
|
297
|
+
`model_reasoning_effort="${input.reasoningEffort}"`,
|
|
298
|
+
'-',
|
|
299
|
+
];
|
|
300
|
+
if (sandboxMode === 'workspace-write') {
|
|
301
|
+
args.splice(args.length - 3, 0, '-c', `sandbox_workspace_write.network_access=${input.networkAccess ? 'true' : 'false'}`);
|
|
302
|
+
}
|
|
303
|
+
return args;
|
|
304
|
+
}
|
|
305
|
+
function readCodexSandboxMode(env = process.env) {
|
|
306
|
+
const raw = env.PLAYDROP_WORKER_CODEX_SANDBOX?.trim();
|
|
307
|
+
if (!raw) {
|
|
308
|
+
return 'danger-full-access';
|
|
309
|
+
}
|
|
310
|
+
if (raw === 'danger-full-access' || raw === 'workspace-write' || raw === 'read-only') {
|
|
311
|
+
return raw;
|
|
312
|
+
}
|
|
313
|
+
throw new Error(`invalid_playdrop_worker_codex_sandbox:${raw}`);
|
|
314
|
+
}
|
|
315
|
+
function buildClaudeExecArgs(input) {
|
|
316
|
+
const model = input.model.trim();
|
|
317
|
+
if (!model) {
|
|
318
|
+
throw new Error('agent_task_assignment_model_missing');
|
|
319
|
+
}
|
|
320
|
+
const permissionSettings = buildClaudePermissionSettings(input.denyReadRoots ?? []);
|
|
321
|
+
const tools = 'Bash,Edit,Write,Read,Glob,Grep';
|
|
322
|
+
const args = [
|
|
323
|
+
'-p',
|
|
324
|
+
'--model',
|
|
325
|
+
model,
|
|
326
|
+
'--output-format',
|
|
327
|
+
'stream-json',
|
|
328
|
+
'--verbose',
|
|
329
|
+
'--include-partial-messages',
|
|
330
|
+
'--no-session-persistence',
|
|
331
|
+
'--permission-mode',
|
|
332
|
+
'acceptEdits',
|
|
333
|
+
'--tools',
|
|
334
|
+
tools,
|
|
335
|
+
'--allowedTools',
|
|
336
|
+
...CLAUDE_ALLOWED_PERMISSION_RULES,
|
|
337
|
+
'--disallowedTools',
|
|
338
|
+
...buildClaudeDeniedPermissionRules(input.denyReadRoots ?? []),
|
|
339
|
+
'--settings',
|
|
340
|
+
JSON.stringify(permissionSettings),
|
|
341
|
+
];
|
|
342
|
+
const pluginDir = typeof input.pluginDir === 'string' ? input.pluginDir.trim() : '';
|
|
343
|
+
if (pluginDir) {
|
|
344
|
+
args.push('--plugin-dir', pluginDir);
|
|
345
|
+
}
|
|
346
|
+
const effort = typeof input.effort === 'string' ? input.effort.trim() : '';
|
|
347
|
+
if (effort) {
|
|
348
|
+
args.push('--effort', effort);
|
|
349
|
+
}
|
|
350
|
+
return args;
|
|
351
|
+
}
|
|
352
|
+
const CLAUDE_ALLOWED_PERMISSION_RULES = [
|
|
353
|
+
'Read',
|
|
354
|
+
'Edit',
|
|
355
|
+
'Write',
|
|
356
|
+
'Glob',
|
|
357
|
+
'Grep',
|
|
358
|
+
'Bash',
|
|
359
|
+
'Bash(playdrop *)',
|
|
360
|
+
'Bash(./bin/playdrop *)',
|
|
361
|
+
'Bash(bin/playdrop *)',
|
|
362
|
+
'Bash(/Users/*/PlayDropWorker/tasks/*/bin/playdrop *)',
|
|
363
|
+
'Bash(node *)',
|
|
364
|
+
'Bash(npm *)',
|
|
365
|
+
'Bash(mkdir *)',
|
|
366
|
+
'Bash(cp *)',
|
|
367
|
+
'Bash(mv *)',
|
|
368
|
+
'Bash(file *)',
|
|
369
|
+
'Bash(sips *)',
|
|
370
|
+
'Bash(ls *)',
|
|
371
|
+
'Bash(pwd)',
|
|
372
|
+
'Bash(cat *)',
|
|
373
|
+
'Bash(head *)',
|
|
374
|
+
'Bash(tail *)',
|
|
375
|
+
'Bash(rg *)',
|
|
376
|
+
'Bash(grep *)',
|
|
377
|
+
'Bash(find *)',
|
|
378
|
+
'Bash(which *)',
|
|
379
|
+
'Bash(wc *)',
|
|
380
|
+
'Bash(stat *)',
|
|
381
|
+
'Bash(du *)',
|
|
382
|
+
];
|
|
383
|
+
function normalizeClaudePermissionPath(value) {
|
|
384
|
+
const trimmed = value.trim();
|
|
385
|
+
if (!trimmed) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
return node_path_1.default.resolve(trimmed).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
389
|
+
}
|
|
390
|
+
function buildClaudePermissionSettings(denyReadRoots = []) {
|
|
391
|
+
return {
|
|
392
|
+
permissions: {
|
|
393
|
+
allow: [...CLAUDE_ALLOWED_PERMISSION_RULES],
|
|
394
|
+
deny: buildClaudeDeniedPermissionRules(denyReadRoots),
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function buildClaudeDeniedPermissionRules(denyReadRoots = []) {
|
|
399
|
+
const deny = new Set([
|
|
400
|
+
'Bash(rm *)',
|
|
401
|
+
'Bash(git *)',
|
|
402
|
+
]);
|
|
403
|
+
for (const rawRoot of denyReadRoots) {
|
|
404
|
+
const root = normalizeClaudePermissionPath(rawRoot);
|
|
405
|
+
if (!root) {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
deny.add(`Read(//${root.replace(/^\/+/, '')}/**)`);
|
|
409
|
+
deny.add(`Bash(* ${root}*)`);
|
|
410
|
+
}
|
|
411
|
+
return [...deny];
|
|
412
|
+
}
|
|
413
|
+
function buildWorkerChildEnv(input) {
|
|
414
|
+
const base = input.baseEnv ?? process.env;
|
|
415
|
+
const child = {};
|
|
416
|
+
for (const key of ['HOME', 'USER', 'SHELL', 'TMPDIR', 'LANG', 'TERM']) {
|
|
417
|
+
const value = base[key];
|
|
418
|
+
if (typeof value === 'string') {
|
|
419
|
+
child[key] = value;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
for (const [key, value] of Object.entries(base)) {
|
|
423
|
+
if (key.startsWith('LC_') && typeof value === 'string') {
|
|
424
|
+
child[key] = value;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
child.PATH = typeof base.PATH === 'string' && base.PATH
|
|
428
|
+
? `${input.binDir}${node_path_1.default.delimiter}${base.PATH}`
|
|
429
|
+
: input.binDir;
|
|
430
|
+
for (const key of [
|
|
431
|
+
'PLAYDROP_API_BASE',
|
|
432
|
+
`PLAYDROP_API_BASE_${input.envName.toUpperCase()}`,
|
|
433
|
+
'PLAYDROP_AI_BASE',
|
|
434
|
+
`PLAYDROP_AI_BASE_${input.envName.toUpperCase()}`,
|
|
435
|
+
]) {
|
|
436
|
+
const value = base[key];
|
|
437
|
+
if (typeof value === 'string') {
|
|
438
|
+
child[key] = value;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
child.PLAYDROP_WORKER_CONTEXT = '1';
|
|
442
|
+
if (!Number.isInteger(input.attempt) || input.attempt <= 0) {
|
|
443
|
+
throw new Error('invalid_worker_task_attempt');
|
|
444
|
+
}
|
|
445
|
+
child.PLAYDROP_WORKER_TASK_ID = String(input.taskId);
|
|
446
|
+
child.PLAYDROP_WORKER_TASK_ATTEMPT = String(input.attempt);
|
|
447
|
+
if (input.devPort !== undefined) {
|
|
448
|
+
if (!Number.isInteger(input.devPort) || input.devPort <= 0 || input.devPort > 65535) {
|
|
449
|
+
throw new Error('invalid_worker_task_dev_port');
|
|
450
|
+
}
|
|
451
|
+
child.PLAYDROP_WORKER_TASK_DEV_PORT = String(input.devPort);
|
|
452
|
+
child.PLAYDROP_DEV_ROUTER_PORT = String(input.devPort);
|
|
453
|
+
}
|
|
454
|
+
if (input.eventDir?.trim()) {
|
|
455
|
+
child.PLAYDROP_WORKER_EVENT_DIR = input.eventDir.trim();
|
|
456
|
+
}
|
|
457
|
+
return child;
|
|
458
|
+
}
|