Skip to content

Commit 3af6bdf

Browse files
committed
Auto-merge upstream openclaw/openclaw
2 parents 9911ea5 + 545490c commit 3af6bdf

11 files changed

Lines changed: 260 additions & 158 deletions

File tree

extensions/codex/src/app-server/run-attempt.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,56 @@ describe("runCodexAppServerAttempt", () => {
119119
);
120120
});
121121

122+
it("does not leak unhandled rejections when shutdown closes before interrupt", async () => {
123+
const unhandledRejections: unknown[] = [];
124+
const onUnhandledRejection = (reason: unknown) => {
125+
unhandledRejections.push(reason);
126+
};
127+
process.on("unhandledRejection", onUnhandledRejection);
128+
try {
129+
const requests: Array<{ method: string; params: unknown }> = [];
130+
const request = vi.fn(async (method: string, params?: unknown) => {
131+
requests.push({ method, params });
132+
if (method === "thread/start") {
133+
return { thread: { id: "thread-1" }, model: "gpt-5.4-codex", modelProvider: "openai" };
134+
}
135+
if (method === "turn/start") {
136+
return { turn: { id: "turn-1", status: "inProgress" } };
137+
}
138+
if (method === "turn/interrupt") {
139+
throw new Error("codex app-server client is closed");
140+
}
141+
return {};
142+
});
143+
__testing.setCodexAppServerClientFactoryForTests(
144+
async () =>
145+
({
146+
request,
147+
addNotificationHandler: () => () => undefined,
148+
addRequestHandler: () => () => undefined,
149+
}) as never,
150+
);
151+
const abortController = new AbortController();
152+
const params = createParams(
153+
path.join(tempDir, "session.jsonl"),
154+
path.join(tempDir, "workspace"),
155+
);
156+
params.abortSignal = abortController.signal;
157+
158+
const run = runCodexAppServerAttempt(params);
159+
await vi.waitFor(() =>
160+
expect(requests.some((entry) => entry.method === "turn/start")).toBe(true),
161+
);
162+
abortController.abort("shutdown");
163+
164+
await expect(run).resolves.toMatchObject({ aborted: true });
165+
await new Promise((resolve) => setImmediate(resolve));
166+
expect(unhandledRejections).toEqual([]);
167+
} finally {
168+
process.off("unhandledRejection", onUnhandledRejection);
169+
}
170+
});
171+
122172
it("forwards image attachments to the app-server turn input", async () => {
123173
const requests: Array<{ method: string; params: unknown }> = [];
124174
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;

extensions/codex/src/app-server/run-attempt.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,14 @@ export async function runCodexAppServerAttempt(
227227
);
228228

229229
const abortListener = () => {
230-
void client.request("turn/interrupt", {
231-
threadId: thread.threadId,
232-
turnId: activeTurnId,
233-
});
230+
void client
231+
.request("turn/interrupt", {
232+
threadId: thread.threadId,
233+
turnId: activeTurnId,
234+
})
235+
.catch((error: unknown) => {
236+
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
237+
});
234238
resolveCompletion?.();
235239
};
236240
runAbortController.signal.addEventListener("abort", abortListener, { once: true });

extensions/msteams/src/monitor-handler/reaction-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
createChannelPairingController,
99
} from "../../runtime-api.js";
1010
import { normalizeMSTeamsConversationId } from "../inbound.js";
11-
import type { MSTeamsMessageHandlerDeps } from "../monitor-handler.js";
11+
import type { MSTeamsMessageHandlerDeps } from "../monitor-handler.types.js";
1212
import {
1313
isMSTeamsGroupAllowed,
1414
resolveMSTeamsAllowlistMatch,

scripts/e2e/parallels-npm-update-smoke.sh

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ API_KEY_ENV=""
1111
AUTH_CHOICE=""
1212
AUTH_KEY_FLAG=""
1313
MODEL_ID=""
14+
PYTHON_BIN="${PYTHON_BIN:-}"
1415
PACKAGE_SPEC=""
1516
UPDATE_TARGET=""
1617
JSON_OUTPUT=0
@@ -62,6 +63,35 @@ cleanup() {
6263

6364
trap cleanup EXIT
6465

66+
resolve_python_bin() {
67+
local candidate
68+
69+
python_bin_usable() {
70+
"$1" - <<'PY' >/dev/null 2>&1
71+
import sys
72+
if sys.version_info < (3, 10):
73+
raise SystemExit(1)
74+
_value: tuple[int, ...] | None = None
75+
PY
76+
}
77+
78+
if [[ -n "$PYTHON_BIN" ]]; then
79+
[[ -x "$PYTHON_BIN" ]] || die "PYTHON_BIN is not executable: $PYTHON_BIN"
80+
python_bin_usable "$PYTHON_BIN" || die "PYTHON_BIN must be Python 3.10+: $PYTHON_BIN"
81+
return
82+
fi
83+
84+
for candidate in "$(command -v python3 || true)" /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3; do
85+
[[ -n "$candidate" && -x "$candidate" ]] || continue
86+
if python_bin_usable "$candidate"; then
87+
PYTHON_BIN="$candidate"
88+
return
89+
fi
90+
done
91+
92+
die "Python 3.10+ is required"
93+
}
94+
6595
usage() {
6696
cat <<'EOF'
6797
Usage: bash scripts/e2e/parallels-npm-update-smoke.sh [options]
@@ -142,12 +172,13 @@ esac
142172

143173
API_KEY_VALUE="${!API_KEY_ENV:-}"
144174
[[ -n "$API_KEY_VALUE" ]] || die "$API_KEY_ENV is required"
175+
resolve_python_bin
145176

146177
resolve_linux_vm_name() {
147178
local json requested
148179
json="$(prlctl list --all --json)"
149180
requested="$LINUX_VM"
150-
PRL_VM_JSON="$json" REQUESTED_VM_NAME="$requested" python3 - <<'PY'
181+
PRL_VM_JSON="$json" REQUESTED_VM_NAME="$requested" "$PYTHON_BIN" - <<'PY'
151182
import difflib
152183
import json
153184
import os
@@ -220,7 +251,7 @@ resolve_host_ip() {
220251
}
221252

222253
allocate_host_port() {
223-
python3 - <<'PY'
254+
"$PYTHON_BIN" - <<'PY'
224255
import socket
225256
226257
sock = socket.socket()
@@ -242,7 +273,7 @@ pack_main_tgz() {
242273
ensure_current_build
243274
pkg="$(
244275
npm pack --ignore-scripts --json --pack-destination "$MAIN_TGZ_DIR" \
245-
| python3 -c 'import json, sys; data = json.load(sys.stdin); print(data[-1]["filename"])'
276+
| "$PYTHON_BIN" -c 'import json, sys; data = json.load(sys.stdin); print(data[-1]["filename"])'
246277
)"
247278
MAIN_TGZ_PATH="$MAIN_TGZ_DIR/openclaw-main-$CURRENT_HEAD_SHORT.tgz"
248279
cp "$MAIN_TGZ_DIR/$pkg" "$MAIN_TGZ_PATH"
@@ -452,7 +483,7 @@ start_server() {
452483
say "Serve update helper artifacts on $HOST_IP:$HOST_PORT"
453484
(
454485
cd "$MAIN_TGZ_DIR"
455-
exec python3 -m http.server "$HOST_PORT" --bind 0.0.0.0
486+
exec "$PYTHON_BIN" -m http.server "$HOST_PORT" --bind 0.0.0.0
456487
) >/tmp/openclaw-parallels-npm-update-http.log 2>&1 &
457488
SERVER_PID=$!
458489
sleep 1
@@ -475,7 +506,7 @@ wait_job() {
475506

476507
extract_log_progress() {
477508
local log_path="$1"
478-
python3 - "$log_path" <<'PY'
509+
"$PYTHON_BIN" - "$log_path" <<'PY'
479510
import pathlib
480511
import sys
481512
@@ -557,7 +588,7 @@ monitor_jobs_progress() {
557588

558589
extract_last_version() {
559590
local log_path="$1"
560-
python3 - "$log_path" <<'PY'
591+
"$PYTHON_BIN" - "$log_path" <<'PY'
561592
import pathlib
562593
import re
563594
import sys
@@ -573,7 +604,7 @@ guest_powershell() {
573604
local script="$1"
574605
local encoded
575606
encoded="$(
576-
SCRIPT_CONTENT="$script" python3 - <<'PY'
607+
SCRIPT_CONTENT="$script" "$PYTHON_BIN" - <<'PY'
577608
import base64
578609
import os
579610
@@ -588,7 +619,7 @@ PY
588619
host_timeout_exec() {
589620
local timeout_s="$1"
590621
shift
591-
HOST_TIMEOUT_S="$timeout_s" python3 - "$@" <<'PY'
622+
HOST_TIMEOUT_S="$timeout_s" "$PYTHON_BIN" - "$@" <<'PY'
592623
import os
593624
import subprocess
594625
import sys
@@ -670,7 +701,7 @@ guest_powershell_poll() {
670701
local script="$2"
671702
local encoded
672703
encoded="$(
673-
SCRIPT_CONTENT="$script" python3 - <<'PY'
704+
SCRIPT_CONTENT="$script" "$PYTHON_BIN" - <<'PY'
674705
import base64
675706
import os
676707
@@ -734,7 +765,7 @@ EOF
734765
if [[ $log_rc -ne 0 ]] || [[ -z "$guest_log" ]]; then
735766
return "$log_rc"
736767
fi
737-
GUEST_LOG="$guest_log" python3 - "$log_state_path" <<'PY'
768+
GUEST_LOG="$guest_log" "$PYTHON_BIN" - "$log_state_path" <<'PY'
738769
import os
739770
import pathlib
740771
import sys
@@ -893,7 +924,7 @@ EOF
893924

894925
write_summary_json() {
895926
local summary_path="$RUN_DIR/summary.json"
896-
python3 - "$summary_path" <<'PY'
927+
"$PYTHON_BIN" - "$summary_path" <<'PY'
897928
import json
898929
import os
899930
import sys

src/agents/pi-embedded-runner/compact.types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js";
22
import type { OpenClawConfig } from "../../config/types.openclaw.js";
3-
import type { enqueueCommand } from "../../process/command-queue.js";
4-
import type { ExecElevatedDefaults } from "../bash-tools.js";
3+
import type { CommandQueueEnqueueFn } from "../../process/command-queue.types.js";
4+
import type { ExecElevatedDefaults } from "../bash-tools.exec-types.js";
55
import type { SkillSnapshot } from "../skills.js";
66

77
export type CompactEmbeddedPiSessionParams = {
@@ -50,7 +50,7 @@ export type CompactEmbeddedPiSessionParams = {
5050
attempt?: number;
5151
maxAttempts?: number;
5252
lane?: string;
53-
enqueue?: typeof enqueueCommand;
53+
enqueue?: CommandQueueEnqueueFn;
5454
extraSystemPrompt?: string;
5555
ownerNumbers?: string[];
5656
abortSignal?: AbortSignal;

src/agents/pi-embedded-runner/run/params.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import type { ReasoningLevel, ThinkLevel, VerboseLevel } from "../../../auto-rep
44
import type { ReplyPayload } from "../../../auto-reply/types.js";
55
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
66
import type { PromptImageOrderEntry } from "../../../media/prompt-image-order.js";
7-
import type { enqueueCommand } from "../../../process/command-queue.js";
7+
import type { CommandQueueEnqueueFn } from "../../../process/command-queue.types.js";
88
import type { InputProvenance } from "../../../sessions/input-provenance.js";
9-
import type { ExecElevatedDefaults, ExecToolDefaults } from "../../bash-tools.js";
9+
import type { ExecElevatedDefaults, ExecToolDefaults } from "../../bash-tools.exec-types.js";
1010
import type { AgentStreamParams, ClientToolDefinition } from "../../command/shared-types.js";
1111
import type { AgentInternalEvent } from "../../internal-events.js";
1212
import type { BlockReplyPayload } from "../../pi-embedded-payloads.js";
@@ -113,7 +113,7 @@ export type RunEmbeddedPiAgentParams = {
113113
onToolResult?: (payload: ReplyPayload) => void | Promise<void>;
114114
onAgentEvent?: (evt: { stream: string; data: Record<string, unknown> }) => void;
115115
lane?: string;
116-
enqueue?: typeof enqueueCommand;
116+
enqueue?: CommandQueueEnqueueFn;
117117
extraSystemPrompt?: string;
118118
internalEvents?: AgentInternalEvent[];
119119
inputProvenance?: InputProvenance;

0 commit comments

Comments
 (0)