mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
feat(telegram/acp): Topic Binding, Pin Binding Message, Fix Spawn Param Parsing (#36683)
* fix(acp): normalize unicode flags and Telegram topic binding * feat(telegram/acp): restore topic-bound ACP and session bindings * fix(acpx): clarify permission-denied guidance * feat(telegram/acp): pin spawn bind notice in topics * docs(telegram): document ACP topic thread binding behavior * refactor(reply): share Telegram conversation-id resolver * fix(telegram/acp): preserve bound session routing semantics * fix(telegram): respect binding persistence and expiry reporting * refactor(telegram): simplify binding lifecycle persistence * fix(telegram): bind acp spawns in direct messages * fix: document telegram ACP topic binding changelog (#36683) (thanks @huntharo) --------- Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
This commit is contained in:
@@ -223,6 +223,10 @@ if (command === "prompt") {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (stdinText.includes("permission-denied")) {
|
||||
process.exit(5);
|
||||
}
|
||||
|
||||
if (stdinText.includes("split-spacing")) {
|
||||
emitUpdate(sessionFromOption, {
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
|
||||
@@ -224,6 +224,42 @@ describe("AcpxRuntime", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("maps acpx permission-denied exits to actionable guidance", async () => {
|
||||
const runtime = sharedFixture?.runtime;
|
||||
expect(runtime).toBeDefined();
|
||||
if (!runtime) {
|
||||
throw new Error("shared runtime fixture missing");
|
||||
}
|
||||
const handle = await runtime.ensureSession({
|
||||
sessionKey: "agent:codex:acp:permission-denied",
|
||||
agent: "codex",
|
||||
mode: "persistent",
|
||||
});
|
||||
|
||||
const events = [];
|
||||
for await (const event of runtime.runTurn({
|
||||
handle,
|
||||
text: "permission-denied",
|
||||
mode: "prompt",
|
||||
requestId: "req-perm",
|
||||
})) {
|
||||
events.push(event);
|
||||
}
|
||||
|
||||
expect(events).toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: "error",
|
||||
message: expect.stringContaining("Permission denied by ACP runtime (acpx)."),
|
||||
}),
|
||||
);
|
||||
expect(events).toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: "error",
|
||||
message: expect.stringContaining("approve-reads, approve-all, deny-all"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("supports cancel and close using encoded runtime handle state", async () => {
|
||||
const { runtime, logPath, config } = await createMockRuntimeFixture();
|
||||
const handle = await runtime.ensureSession({
|
||||
|
||||
@@ -42,10 +42,30 @@ export const ACPX_BACKEND_ID = "acpx";
|
||||
|
||||
const ACPX_RUNTIME_HANDLE_PREFIX = "acpx:v1:";
|
||||
const DEFAULT_AGENT_FALLBACK = "codex";
|
||||
const ACPX_EXIT_CODE_PERMISSION_DENIED = 5;
|
||||
const ACPX_CAPABILITIES: AcpRuntimeCapabilities = {
|
||||
controls: ["session/set_mode", "session/set_config_option", "session/status"],
|
||||
};
|
||||
|
||||
function formatPermissionModeGuidance(): string {
|
||||
return "Configure plugins.entries.acpx.config.permissionMode to one of: approve-reads, approve-all, deny-all.";
|
||||
}
|
||||
|
||||
function formatAcpxExitMessage(params: {
|
||||
stderr: string;
|
||||
exitCode: number | null | undefined;
|
||||
}): string {
|
||||
const stderr = params.stderr.trim();
|
||||
if (params.exitCode === ACPX_EXIT_CODE_PERMISSION_DENIED) {
|
||||
return [
|
||||
stderr || "Permission denied by ACP runtime (acpx).",
|
||||
"ACPX blocked a write/exec permission request in a non-interactive session.",
|
||||
formatPermissionModeGuidance(),
|
||||
].join(" ");
|
||||
}
|
||||
return stderr || `acpx exited with code ${params.exitCode ?? "unknown"}`;
|
||||
}
|
||||
|
||||
export function encodeAcpxRuntimeHandleState(state: AcpxHandleState): string {
|
||||
const payload = Buffer.from(JSON.stringify(state), "utf8").toString("base64url");
|
||||
return `${ACPX_RUNTIME_HANDLE_PREFIX}${payload}`;
|
||||
@@ -333,7 +353,10 @@ export class AcpxRuntime implements AcpRuntime {
|
||||
if ((exit.code ?? 0) !== 0 && !sawError) {
|
||||
yield {
|
||||
type: "error",
|
||||
message: stderr.trim() || `acpx exited with code ${exit.code ?? "unknown"}`,
|
||||
message: formatAcpxExitMessage({
|
||||
stderr,
|
||||
exitCode: exit.code,
|
||||
}),
|
||||
};
|
||||
return;
|
||||
}
|
||||
@@ -639,7 +662,10 @@ export class AcpxRuntime implements AcpRuntime {
|
||||
if ((result.code ?? 0) !== 0) {
|
||||
throw new AcpRuntimeError(
|
||||
params.fallbackCode,
|
||||
result.stderr.trim() || `acpx exited with code ${result.code ?? "unknown"}`,
|
||||
formatAcpxExitMessage({
|
||||
stderr: result.stderr,
|
||||
exitCode: result.code,
|
||||
}),
|
||||
);
|
||||
}
|
||||
return events;
|
||||
|
||||
Reference in New Issue
Block a user