@pushpalsdev/cli 1.1.13 → 1.1.15
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/dist/pushpals-cli.js +31 -1
- package/package.json +1 -1
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +1 -1
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +63 -0
- package/runtime/sandbox/apps/workerpals/src/common/generic_python_executor.ts +119 -7
package/dist/pushpals-cli.js
CHANGED
|
@@ -1648,6 +1648,7 @@ var DEFAULT_EMBEDDED_SERVICE_LAUNCH_WARN_MS = 5000;
|
|
|
1648
1648
|
var EMBEDDED_SERVICE_RESTART_MAX_ATTEMPTS = 4;
|
|
1649
1649
|
var WORKERPAL_STARTUP_READINESS_PROBE_MAX_MS = 15000;
|
|
1650
1650
|
var CLI_SESSION_JOB_LOG_MAX_CHARS = 700;
|
|
1651
|
+
var CLI_SESSION_SHOW_JOB_EVENTS_ENV = "PUSHPALS_CLI_SHOW_JOB_EVENTS";
|
|
1651
1652
|
var EMBEDDED_RUNTIME_SAFETY_CAP_DISABLE_ENV = "PUSHPALS_DISABLE_EMBEDDED_SAFETY_CAPS";
|
|
1652
1653
|
var EMBEDDED_RUNTIME_WINDOWS_SAFETY_CAPS = {
|
|
1653
1654
|
REMOTEBUDDY_WORKERPAL_STARTUP_TIMEOUT_MS: "120000",
|
|
@@ -1674,6 +1675,12 @@ function formatTimestampedCliLine(line, at = new Date) {
|
|
|
1674
1675
|
}
|
|
1675
1676
|
return `[${at.toISOString()}]${text}`;
|
|
1676
1677
|
}
|
|
1678
|
+
function isTruthyCliEnvValue(value) {
|
|
1679
|
+
return /^(1|true|yes|on)$/i.test(String(value ?? "").trim());
|
|
1680
|
+
}
|
|
1681
|
+
function shouldShowCliSessionOperationalEvents(env = process.env) {
|
|
1682
|
+
return isTruthyCliEnvValue(env[CLI_SESSION_SHOW_JOB_EVENTS_ENV]);
|
|
1683
|
+
}
|
|
1677
1684
|
function formatRuntimeStartupTimingSummary(input) {
|
|
1678
1685
|
const phaseSummary = input.phases.map((phase) => `${phase.name}=${Math.max(0, Math.floor(phase.durationMs))}ms(${phase.status.trim() || "unknown"})`).join(" ");
|
|
1679
1686
|
const detail = typeof input.detail === "string" && input.detail.trim() ? ` detail=${input.detail.trim()}` : "";
|
|
@@ -5218,7 +5225,10 @@ function formatSessionEventLine(event) {
|
|
|
5218
5225
|
const type = String(event.type ?? "").toLowerCase();
|
|
5219
5226
|
const from = String(event.from ?? "");
|
|
5220
5227
|
const payload = event.payload ?? {};
|
|
5228
|
+
const showOperationalEvents = shouldShowCliSessionOperationalEvents();
|
|
5221
5229
|
if (type === "job_enqueued") {
|
|
5230
|
+
if (!showOperationalEvents)
|
|
5231
|
+
return null;
|
|
5222
5232
|
const jobId = String(payload.jobId ?? "").slice(0, 8);
|
|
5223
5233
|
const kind = String(payload.kind ?? "").trim();
|
|
5224
5234
|
const taskId = String(payload.taskId ?? "").slice(0, 8);
|
|
@@ -5226,11 +5236,15 @@ function formatSessionEventLine(event) {
|
|
|
5226
5236
|
return `[job ${jobId}] queued: ${detail}`;
|
|
5227
5237
|
}
|
|
5228
5238
|
if (type === "job_claimed") {
|
|
5239
|
+
if (!showOperationalEvents)
|
|
5240
|
+
return null;
|
|
5229
5241
|
const jobId = String(payload.jobId ?? "").slice(0, 8);
|
|
5230
5242
|
const workerId = String(payload.workerId ?? "").trim();
|
|
5231
5243
|
return `[job ${jobId}] claimed${workerId ? ` by ${workerId}` : ""}`;
|
|
5232
5244
|
}
|
|
5233
5245
|
if (type === "job_log") {
|
|
5246
|
+
if (!showOperationalEvents)
|
|
5247
|
+
return null;
|
|
5234
5248
|
const jobId = String(payload.jobId ?? "").slice(0, 8);
|
|
5235
5249
|
const stream = String(payload.stream ?? "").toLowerCase() === "stderr" ? " stderr" : "";
|
|
5236
5250
|
const phase = compactCliSessionJobLogLine(String(payload.phase ?? "").trim());
|
|
@@ -5239,6 +5253,8 @@ function formatSessionEventLine(event) {
|
|
|
5239
5253
|
return line ? `[job ${jobId}${stream}${phaseLabel}] ${line}` : null;
|
|
5240
5254
|
}
|
|
5241
5255
|
if (type === "job_failed") {
|
|
5256
|
+
if (!showOperationalEvents)
|
|
5257
|
+
return null;
|
|
5242
5258
|
const jobId = String(payload.jobId ?? "").slice(0, 8);
|
|
5243
5259
|
const message = String(payload.message ?? "").trim();
|
|
5244
5260
|
return `[job ${jobId}] failed: ${message || "unknown"}`;
|
|
@@ -5251,24 +5267,34 @@ function formatSessionEventLine(event) {
|
|
|
5251
5267
|
const text = String(payload.text ?? "").trim();
|
|
5252
5268
|
if (!text)
|
|
5253
5269
|
return null;
|
|
5270
|
+
if (/^All systems online\b/i.test(text))
|
|
5271
|
+
return null;
|
|
5254
5272
|
return `assistant> ${text}`;
|
|
5255
5273
|
}
|
|
5256
5274
|
if (type === "task_progress") {
|
|
5275
|
+
if (!showOperationalEvents)
|
|
5276
|
+
return null;
|
|
5257
5277
|
const taskId = String(payload.taskId ?? "").slice(0, 8);
|
|
5258
5278
|
const message = String(payload.message ?? "").trim();
|
|
5259
5279
|
return message ? `[task ${taskId}] ${message}` : null;
|
|
5260
5280
|
}
|
|
5261
5281
|
if (type === "task_failed") {
|
|
5282
|
+
if (!showOperationalEvents)
|
|
5283
|
+
return null;
|
|
5262
5284
|
const taskId = String(payload.taskId ?? "").slice(0, 8);
|
|
5263
5285
|
const message = String(payload.message ?? "").trim();
|
|
5264
5286
|
return `[task ${taskId}] failed: ${message || "unknown"}`;
|
|
5265
5287
|
}
|
|
5266
5288
|
if (type === "task_completed") {
|
|
5289
|
+
if (!showOperationalEvents)
|
|
5290
|
+
return null;
|
|
5267
5291
|
const taskId = String(payload.taskId ?? "").slice(0, 8);
|
|
5268
5292
|
const summary = String(payload.summary ?? "").trim();
|
|
5269
5293
|
return `[task ${taskId}] completed${summary ? `: ${summary}` : ""}`;
|
|
5270
5294
|
}
|
|
5271
5295
|
if (type === "job_completed") {
|
|
5296
|
+
if (!showOperationalEvents)
|
|
5297
|
+
return null;
|
|
5272
5298
|
const jobId = String(payload.jobId ?? "").slice(0, 8);
|
|
5273
5299
|
const summary = String(payload.summary ?? "").trim();
|
|
5274
5300
|
return `[job ${jobId}] completed${summary ? `: ${summary}` : ""}`;
|
|
@@ -5277,6 +5303,8 @@ function formatSessionEventLine(event) {
|
|
|
5277
5303
|
return null;
|
|
5278
5304
|
}
|
|
5279
5305
|
if (type === "status") {
|
|
5306
|
+
if (!showOperationalEvents)
|
|
5307
|
+
return null;
|
|
5280
5308
|
const state = String(payload.state ?? "").trim();
|
|
5281
5309
|
const detail = String(payload.detail ?? "").trim();
|
|
5282
5310
|
const source = from || String(payload.agentId ?? "status");
|
|
@@ -5315,8 +5343,9 @@ function shouldSuppressCliSessionJobLogLine(line) {
|
|
|
5315
5343
|
return true;
|
|
5316
5344
|
if (/^\[QualityGate\]\s+(?:Policy:|Gates:)/i.test(text))
|
|
5317
5345
|
return true;
|
|
5318
|
-
if (/^\[
|
|
5346
|
+
if (/^\[(?:Openai_codex|OpenHands|Miniswe)Executor\]\s+(?:Spawning\b|Timeout reached\b|Still running\b|Process did not exit after graceful timeout termination\b)/i.test(text)) {
|
|
5319
5347
|
return true;
|
|
5348
|
+
}
|
|
5320
5349
|
if (/^\[OpenAICodexExecutor\]\s+(?:Planner guidance|Codex auth mode|ChatGPT auth mode|Starting codex exec|codex exec finished|Codex JSON stream captured|Codex stdout captured|No reasoning-like|Reasoning-like event|Usage observed|Temporarily masked repo-local|Timeout reached after|Process did not exit after graceful timeout termination)/i.test(text)) {
|
|
5321
5350
|
return true;
|
|
5322
5351
|
}
|
|
@@ -6008,6 +6037,7 @@ export {
|
|
|
6008
6037
|
startEmbeddedMonitoringHub,
|
|
6009
6038
|
shutdownEmbeddedServiceManagerGracefully,
|
|
6010
6039
|
shouldUseRemoteBuddySilentStartupFallback,
|
|
6040
|
+
shouldShowCliSessionOperationalEvents,
|
|
6011
6041
|
shouldRunEmbeddedRuntimeStartupPrechecks,
|
|
6012
6042
|
shouldRestartEmbeddedService,
|
|
6013
6043
|
runCommandWithEnv,
|
package/package.json
CHANGED
|
@@ -1892,7 +1892,7 @@ def _run_codex_task(
|
|
|
1892
1892
|
command_policy_rejection_loop = False
|
|
1893
1893
|
no_edit_watchdog_s = (
|
|
1894
1894
|
_resolve_no_edit_watchdog_seconds(prompt, communicate_timeout_s)
|
|
1895
|
-
if no_edit_recovery_attempt
|
|
1895
|
+
if no_edit_recovery_attempt <= _MAX_NO_EDIT_RECOVERY_ATTEMPTS
|
|
1896
1896
|
else None
|
|
1897
1897
|
)
|
|
1898
1898
|
no_edit_deadline = (
|
|
@@ -823,6 +823,69 @@ class OpenAICodexRuntimeConfigTests(unittest.TestCase):
|
|
|
823
823
|
self.assertIn("Patched immediately after no-edit recovery", str(result.get("stdout") or ""))
|
|
824
824
|
self.assertIn("src/", str(result.get("stdout") or ""))
|
|
825
825
|
|
|
826
|
+
def test_run_codex_task_recovery_attempt_is_still_guarded_by_no_edit_watchdog(self) -> None:
|
|
827
|
+
with tempfile.TemporaryDirectory(prefix="pushpals-codex-no-edit-watchdog-fail-") as temp_dir:
|
|
828
|
+
repo = Path(temp_dir) / "repo"
|
|
829
|
+
repo.mkdir(parents=True, exist_ok=True)
|
|
830
|
+
(repo / "README.md").write_text("# no edit watchdog failure repo\n", encoding="utf-8")
|
|
831
|
+
subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True, text=True)
|
|
832
|
+
subprocess.run(
|
|
833
|
+
["git", "config", "user.name", "PushPals Test"],
|
|
834
|
+
cwd=repo,
|
|
835
|
+
check=True,
|
|
836
|
+
capture_output=True,
|
|
837
|
+
text=True,
|
|
838
|
+
)
|
|
839
|
+
subprocess.run(
|
|
840
|
+
["git", "config", "user.email", "pushpals-tests@example.com"],
|
|
841
|
+
cwd=repo,
|
|
842
|
+
check=True,
|
|
843
|
+
capture_output=True,
|
|
844
|
+
text=True,
|
|
845
|
+
)
|
|
846
|
+
subprocess.run(["git", "add", "README.md"], cwd=repo, check=True, capture_output=True, text=True)
|
|
847
|
+
subprocess.run(
|
|
848
|
+
["git", "commit", "-m", "chore: seed no-edit watchdog failure repo"],
|
|
849
|
+
cwd=repo,
|
|
850
|
+
check=True,
|
|
851
|
+
capture_output=True,
|
|
852
|
+
text=True,
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
stub_path = Path(temp_dir) / "fake_codex_no_edit_watchdog_fail.py"
|
|
856
|
+
stub_path.write_text(
|
|
857
|
+
"\n".join(
|
|
858
|
+
[
|
|
859
|
+
"import sys",
|
|
860
|
+
"import time",
|
|
861
|
+
"",
|
|
862
|
+
"sys.stdin.read()",
|
|
863
|
+
"print('item.completed | Still inspecting, no patch yet.', flush=True)",
|
|
864
|
+
"time.sleep(10)",
|
|
865
|
+
]
|
|
866
|
+
),
|
|
867
|
+
encoding="utf-8",
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
env_overrides = {
|
|
871
|
+
"PUSHPALS_OPENAI_CODEX_BIN_JSON": json.dumps([sys.executable, str(stub_path)]),
|
|
872
|
+
"PUSHPALS_OPENAI_CODEX_AUTH_MODE": "api_key",
|
|
873
|
+
"OPENAI_API_KEY": "pushpals-no-edit-watchdog-fail-test-key",
|
|
874
|
+
"WORKERPALS_OPENAI_CODEX_TIMEOUT_S": "20",
|
|
875
|
+
"WORKERPALS_OPENAI_CODEX_NO_EDIT_WATCHDOG_S": "1",
|
|
876
|
+
"WORKERPALS_OPENAI_CODEX_PROGRESS_LOG_INTERVAL_S": "1",
|
|
877
|
+
}
|
|
878
|
+
with mock.patch.dict(os.environ, env_overrides, clear=False):
|
|
879
|
+
result = _run_codex_task(
|
|
880
|
+
str(repo),
|
|
881
|
+
"Polish the first-entry home shell with a compact visual patch.",
|
|
882
|
+
[],
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
self.assertFalse(result.get("ok"), result)
|
|
886
|
+
self.assertEqual(result.get("exitCode"), 124)
|
|
887
|
+
self.assertIn("no publishable changes", str(result.get("summary") or ""))
|
|
888
|
+
|
|
826
889
|
def test_codex_changed_paths_filters_dependency_artifacts_from_publishable_delta(self) -> None:
|
|
827
890
|
with tempfile.TemporaryDirectory(prefix="pushpals-codex-artifact-delta-") as temp_dir:
|
|
828
891
|
repo = Path(temp_dir) / "repo"
|
|
@@ -28,6 +28,8 @@ interface GenericPythonExecutorConfig {
|
|
|
28
28
|
capTimeoutToExecutionBudget?: boolean;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
const BACKEND_TIMEOUT_RESULT_GRACE_MS = 30_000;
|
|
32
|
+
|
|
31
33
|
function estimateTokensFromText(text: string): number {
|
|
32
34
|
return Math.max(0, Math.ceil(String(text ?? "").length / 3));
|
|
33
35
|
}
|
|
@@ -123,6 +125,7 @@ function resolveRuntimeSettings(
|
|
|
123
125
|
export function resolveGenericPythonExecutorTimeoutMs(params: {
|
|
124
126
|
configuredTimeoutMs: number;
|
|
125
127
|
executionBudgetMs?: number | null;
|
|
128
|
+
finalizationBudgetMs?: number | null;
|
|
126
129
|
capTimeoutToExecutionBudget?: boolean;
|
|
127
130
|
}): number {
|
|
128
131
|
const configuredTimeoutMs = Math.max(10_000, Math.floor(params.configuredTimeoutMs));
|
|
@@ -130,12 +133,49 @@ export function resolveGenericPythonExecutorTimeoutMs(params: {
|
|
|
130
133
|
typeof params.executionBudgetMs === "number" && Number.isFinite(params.executionBudgetMs)
|
|
131
134
|
? Math.max(10_000, Math.floor(params.executionBudgetMs))
|
|
132
135
|
: null;
|
|
136
|
+
const finalizationBudgetMs =
|
|
137
|
+
typeof params.finalizationBudgetMs === "number" && Number.isFinite(params.finalizationBudgetMs)
|
|
138
|
+
? Math.max(0, Math.floor(params.finalizationBudgetMs))
|
|
139
|
+
: 0;
|
|
133
140
|
if (executionBudgetMs != null && params.capTimeoutToExecutionBudget !== false) {
|
|
134
|
-
return Math.min(configuredTimeoutMs, executionBudgetMs);
|
|
141
|
+
return Math.min(configuredTimeoutMs, executionBudgetMs + finalizationBudgetMs);
|
|
135
142
|
}
|
|
136
143
|
return configuredTimeoutMs;
|
|
137
144
|
}
|
|
138
145
|
|
|
146
|
+
export function resolveGenericPythonExecutorChildTimeoutMs(params: {
|
|
147
|
+
backendName: string;
|
|
148
|
+
hostTimeoutMs: number;
|
|
149
|
+
executionBudgetMs?: number | null;
|
|
150
|
+
}): number | null {
|
|
151
|
+
const hostTimeoutMs = Math.max(10_000, Math.floor(params.hostTimeoutMs));
|
|
152
|
+
if (params.backendName !== "openai_codex") return null;
|
|
153
|
+
const executionBudgetMs =
|
|
154
|
+
typeof params.executionBudgetMs === "number" && Number.isFinite(params.executionBudgetMs)
|
|
155
|
+
? Math.max(10_000, Math.floor(params.executionBudgetMs))
|
|
156
|
+
: null;
|
|
157
|
+
const childBudgetMs =
|
|
158
|
+
executionBudgetMs == null ? hostTimeoutMs : Math.min(hostTimeoutMs, executionBudgetMs);
|
|
159
|
+
const graceMs = Math.min(
|
|
160
|
+
BACKEND_TIMEOUT_RESULT_GRACE_MS,
|
|
161
|
+
Math.max(2_000, Math.floor(childBudgetMs / 10)),
|
|
162
|
+
);
|
|
163
|
+
return Math.max(1_000, childBudgetMs - graceMs);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function resolveGenericPythonExecutorChildTimeoutEnv(params: {
|
|
167
|
+
backendName: string;
|
|
168
|
+
hostTimeoutMs: number;
|
|
169
|
+
executionBudgetMs?: number | null;
|
|
170
|
+
}): Record<string, string> {
|
|
171
|
+
const childTimeoutMs = resolveGenericPythonExecutorChildTimeoutMs(params);
|
|
172
|
+
if (childTimeoutMs == null) return {};
|
|
173
|
+
return {
|
|
174
|
+
WORKERPALS_OPENAI_CODEX_TIMEOUT_MS: String(childTimeoutMs),
|
|
175
|
+
WORKERPALS_OPENAI_CODEX_TIMEOUT_S: String(Math.max(1, Math.floor(childTimeoutMs / 1000))),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
139
179
|
function toSnakeConfigKey(key: string): string {
|
|
140
180
|
return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
141
181
|
}
|
|
@@ -144,6 +184,7 @@ function formatGenericPythonExecutorTimeoutDetail(
|
|
|
144
184
|
config: GenericPythonExecutorConfig,
|
|
145
185
|
configuredTimeoutMs: number,
|
|
146
186
|
executionBudgetMs: number | null,
|
|
187
|
+
finalizationBudgetMs: number | null,
|
|
147
188
|
timeoutMs: number,
|
|
148
189
|
): string {
|
|
149
190
|
const configPath = `workerpals.${toSnakeConfigKey(config.timeoutConfigKey)}`;
|
|
@@ -154,11 +195,59 @@ function formatGenericPythonExecutorTimeoutDetail(
|
|
|
154
195
|
return `${configPath}=${configuredTimeoutMs}ms; planning executionBudgetMs=${executionBudgetMs}ms ignored by backend opt-out`;
|
|
155
196
|
}
|
|
156
197
|
if (timeoutMs < configuredTimeoutMs) {
|
|
157
|
-
|
|
198
|
+
const finalizationDetail =
|
|
199
|
+
finalizationBudgetMs && finalizationBudgetMs > 0
|
|
200
|
+
? ` + finalizationBudgetMs=${finalizationBudgetMs}ms`
|
|
201
|
+
: "";
|
|
202
|
+
return `${configPath}=${configuredTimeoutMs}ms capped by planning executionBudgetMs=${executionBudgetMs}ms${finalizationDetail}`;
|
|
158
203
|
}
|
|
159
204
|
return `${configPath}=${configuredTimeoutMs}ms within planning executionBudgetMs=${executionBudgetMs}ms`;
|
|
160
205
|
}
|
|
161
206
|
|
|
207
|
+
export function normalizeGenericPythonExecutorParsedResultForTimeout(params: {
|
|
208
|
+
backendName: string;
|
|
209
|
+
kind: string;
|
|
210
|
+
timedOut: boolean;
|
|
211
|
+
timeoutMs: number;
|
|
212
|
+
timeoutDetail?: string;
|
|
213
|
+
summary: string;
|
|
214
|
+
stdout: string;
|
|
215
|
+
stderr: string;
|
|
216
|
+
exitCode: number;
|
|
217
|
+
}): { summary: string; stdout: string; stderr: string; exitCode: number } {
|
|
218
|
+
const signalTerminatedCodex =
|
|
219
|
+
params.timedOut &&
|
|
220
|
+
params.backendName === "openai_codex" &&
|
|
221
|
+
/\bopenai_codex interrupted by signal 15\b/i.test(params.summary);
|
|
222
|
+
if (!signalTerminatedCodex) {
|
|
223
|
+
return {
|
|
224
|
+
summary: params.summary,
|
|
225
|
+
stdout: params.stdout,
|
|
226
|
+
stderr: params.stderr,
|
|
227
|
+
exitCode: params.exitCode,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const timeoutDetail = String(params.timeoutDetail ?? "").trim();
|
|
232
|
+
const cleanedStderr = String(params.stderr ?? "")
|
|
233
|
+
.replace(/\bopenai_codex interrupted by signal 15\b/gi, "OpenAI Codex exceeded the execution budget")
|
|
234
|
+
.trim();
|
|
235
|
+
const stderr = [
|
|
236
|
+
`OpenAI Codex exceeded the PushPals execution budget before returning a completed result.`,
|
|
237
|
+
timeoutDetail ? `Timeout detail: ${timeoutDetail}.` : "",
|
|
238
|
+
cleanedStderr ? `Last stderr:\n${cleanedStderr}` : "",
|
|
239
|
+
]
|
|
240
|
+
.filter(Boolean)
|
|
241
|
+
.join("\n");
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
summary: `${params.backendName} execution budget expired after ${params.timeoutMs}ms for ${params.kind}`,
|
|
245
|
+
stdout: params.stdout,
|
|
246
|
+
stderr,
|
|
247
|
+
exitCode: 124,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
162
251
|
export function createGenericPythonExecutor(
|
|
163
252
|
config: GenericPythonExecutorConfig,
|
|
164
253
|
): BackendTaskExecutor {
|
|
@@ -190,15 +279,21 @@ export function createGenericPythonExecutor(
|
|
|
190
279
|
typeof budgets?.executionBudgetMs === "number" && Number.isFinite(budgets.executionBudgetMs)
|
|
191
280
|
? Math.max(10_000, Math.floor(budgets.executionBudgetMs))
|
|
192
281
|
: null;
|
|
282
|
+
const finalizationBudgetMs =
|
|
283
|
+
typeof budgets?.finalizationBudgetMs === "number" && Number.isFinite(budgets.finalizationBudgetMs)
|
|
284
|
+
? Math.max(0, Math.floor(budgets.finalizationBudgetMs))
|
|
285
|
+
: null;
|
|
193
286
|
const timeoutMs = resolveGenericPythonExecutorTimeoutMs({
|
|
194
287
|
configuredTimeoutMs,
|
|
195
288
|
executionBudgetMs,
|
|
289
|
+
finalizationBudgetMs,
|
|
196
290
|
capTimeoutToExecutionBudget: config.capTimeoutToExecutionBudget,
|
|
197
291
|
});
|
|
198
292
|
const timeoutDetail = formatGenericPythonExecutorTimeoutDetail(
|
|
199
293
|
config,
|
|
200
294
|
configuredTimeoutMs,
|
|
201
295
|
executionBudgetMs,
|
|
296
|
+
finalizationBudgetMs,
|
|
202
297
|
timeoutMs,
|
|
203
298
|
);
|
|
204
299
|
const payloadBase64 = Buffer.from(
|
|
@@ -210,6 +305,11 @@ export function createGenericPythonExecutor(
|
|
|
210
305
|
"utf-8",
|
|
211
306
|
).toString("base64");
|
|
212
307
|
const args = [pythonBin, scriptPath, payloadBase64];
|
|
308
|
+
const childTimeoutEnv = resolveGenericPythonExecutorChildTimeoutEnv({
|
|
309
|
+
backendName,
|
|
310
|
+
hostTimeoutMs: timeoutMs,
|
|
311
|
+
executionBudgetMs,
|
|
312
|
+
});
|
|
213
313
|
|
|
214
314
|
onLog?.(
|
|
215
315
|
"stdout",
|
|
@@ -229,6 +329,7 @@ export function createGenericPythonExecutor(
|
|
|
229
329
|
stderr: "pipe",
|
|
230
330
|
env: {
|
|
231
331
|
...buildWorkerSandboxWritableEnv(repo),
|
|
332
|
+
...childTimeoutEnv,
|
|
232
333
|
PUSHPALS_REPO_PATH: repo,
|
|
233
334
|
PUSHPALS_ASSIGNED_REPO_ROOT: repo,
|
|
234
335
|
PYTHONIOENCODING: "utf-8",
|
|
@@ -333,16 +434,27 @@ export function createGenericPythonExecutor(
|
|
|
333
434
|
parsed.usage,
|
|
334
435
|
estimateJobTokenUsage(backendName, modelId, params, summary, parsedStdout, parsedStderr),
|
|
335
436
|
);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
437
|
+
const normalized = normalizeGenericPythonExecutorParsedResultForTimeout({
|
|
438
|
+
backendName,
|
|
439
|
+
kind,
|
|
440
|
+
timedOut,
|
|
441
|
+
timeoutMs,
|
|
442
|
+
timeoutDetail,
|
|
339
443
|
summary,
|
|
340
|
-
stdout:
|
|
341
|
-
stderr:
|
|
444
|
+
stdout: parsedStdout,
|
|
445
|
+
stderr: parsedStderr,
|
|
342
446
|
exitCode:
|
|
343
447
|
typeof parsed.exitCode === "number" && Number.isFinite(parsed.exitCode)
|
|
344
448
|
? parsed.exitCode
|
|
345
449
|
: exitCode,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
ok: typeof parsed.ok === "boolean" ? parsed.ok : exitCode === 0,
|
|
454
|
+
summary: normalized.summary,
|
|
455
|
+
stdout: truncate(normalized.stdout, outputPolicy),
|
|
456
|
+
stderr: truncate(normalized.stderr, outputPolicy),
|
|
457
|
+
exitCode: normalized.exitCode,
|
|
346
458
|
usage,
|
|
347
459
|
};
|
|
348
460
|
} catch (err) {
|