mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-25 16:12:13 +00:00
test: move send-keys validation to helper
This commit is contained in:
49
src/agents/bash-tools.process-send-keys.test.ts
Normal file
49
src/agents/bash-tools.process-send-keys.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { createProcessSessionFixture } from "./bash-process-registry.test-helpers.js";
|
||||
import { handleProcessSendKeys, type WritableStdin } from "./bash-tools.process-send-keys.js";
|
||||
|
||||
function createWritableStdinStub(): WritableStdin {
|
||||
return {
|
||||
write(_data: string, cb?: (err?: Error | null) => void) {
|
||||
cb?.();
|
||||
},
|
||||
end() {},
|
||||
destroyed: false,
|
||||
};
|
||||
}
|
||||
|
||||
test("process send-keys fails loud for unknown cursor mode when arrows depend on it", async () => {
|
||||
const result = await handleProcessSendKeys({
|
||||
sessionId: "sess-unknown-mode",
|
||||
session: createProcessSessionFixture({
|
||||
id: "sess-unknown-mode",
|
||||
command: "vim",
|
||||
backgrounded: true,
|
||||
cursorKeyMode: "unknown",
|
||||
}),
|
||||
stdin: createWritableStdinStub(),
|
||||
keys: ["up"],
|
||||
});
|
||||
|
||||
expect(result.details).toMatchObject({ status: "failed" });
|
||||
expect(result.content[0]).toMatchObject({
|
||||
type: "text",
|
||||
text: expect.stringContaining("cursor key mode is not known yet"),
|
||||
});
|
||||
});
|
||||
|
||||
test("process send-keys still sends non-cursor keys while mode is unknown", async () => {
|
||||
const result = await handleProcessSendKeys({
|
||||
sessionId: "sess-unknown-enter",
|
||||
session: createProcessSessionFixture({
|
||||
id: "sess-unknown-enter",
|
||||
command: "vim",
|
||||
backgrounded: true,
|
||||
cursorKeyMode: "unknown",
|
||||
}),
|
||||
stdin: createWritableStdinStub(),
|
||||
keys: ["Enter"],
|
||||
});
|
||||
|
||||
expect(result.details).toMatchObject({ status: "running" });
|
||||
});
|
||||
76
src/agents/bash-tools.process-send-keys.ts
Normal file
76
src/agents/bash-tools.process-send-keys.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import type { ProcessSession } from "./bash-process-registry.js";
|
||||
import { deriveSessionName } from "./bash-tools.shared.js";
|
||||
import { encodeKeySequence, hasCursorModeSensitiveKeys } from "./pty-keys.js";
|
||||
|
||||
export type WritableStdin = {
|
||||
write: (data: string, cb?: (err?: Error | null) => void) => void;
|
||||
end: () => void;
|
||||
destroyed?: boolean;
|
||||
};
|
||||
|
||||
function failText(text: string): AgentToolResult<unknown> {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text,
|
||||
},
|
||||
],
|
||||
details: { status: "failed" },
|
||||
};
|
||||
}
|
||||
|
||||
async function writeToStdin(stdin: WritableStdin, data: string) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
stdin.write(data, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function handleProcessSendKeys(params: {
|
||||
sessionId: string;
|
||||
session: ProcessSession;
|
||||
stdin: WritableStdin;
|
||||
keys?: string[];
|
||||
hex?: string[];
|
||||
literal?: string;
|
||||
}): Promise<AgentToolResult<unknown>> {
|
||||
const request = {
|
||||
keys: params.keys,
|
||||
hex: params.hex,
|
||||
literal: params.literal,
|
||||
};
|
||||
if (params.session.cursorKeyMode === "unknown" && hasCursorModeSensitiveKeys(request)) {
|
||||
return failText(
|
||||
`Session ${params.sessionId} cursor key mode is not known yet. Poll or log until startup output appears, then retry send-keys.`,
|
||||
);
|
||||
}
|
||||
const cursorKeyMode =
|
||||
params.session.cursorKeyMode === "unknown" ? undefined : params.session.cursorKeyMode;
|
||||
const { data, warnings } = encodeKeySequence(request, cursorKeyMode);
|
||||
if (!data) {
|
||||
return failText("No key data provided.");
|
||||
}
|
||||
await writeToStdin(params.stdin, data);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
`Sent ${data.length} bytes to session ${params.sessionId}.` +
|
||||
(warnings.length ? `\nWarnings:\n- ${warnings.join("\n- ")}` : ""),
|
||||
},
|
||||
],
|
||||
details: {
|
||||
status: "running",
|
||||
sessionId: params.sessionId,
|
||||
name: deriveSessionName(params.session.command),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,23 +1,8 @@
|
||||
import { afterEach, expect, test } from "vitest";
|
||||
import {
|
||||
addSession,
|
||||
markBackgrounded,
|
||||
resetProcessRegistryForTests,
|
||||
} from "./bash-process-registry.js";
|
||||
import { createProcessSessionFixture } from "./bash-process-registry.test-helpers.js";
|
||||
import { markBackgrounded, resetProcessRegistryForTests } from "./bash-process-registry.js";
|
||||
import { runExecProcess } from "./bash-tools.exec-runtime.js";
|
||||
import { createProcessTool } from "./bash-tools.process.js";
|
||||
|
||||
function createWritableStdinStub() {
|
||||
return {
|
||||
write(_data: string, cb?: (err?: Error | null) => void) {
|
||||
cb?.();
|
||||
},
|
||||
end() {},
|
||||
destroyed: false,
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
resetProcessRegistryForTests();
|
||||
});
|
||||
@@ -103,47 +88,3 @@ test("process submit sends Enter for pty sessions", async () => {
|
||||
|
||||
await waitForSessionCompletion({ processTool, sessionId, expectedText: "submitted" });
|
||||
});
|
||||
|
||||
test("process send-keys fails loud for unknown cursor mode when arrows depend on it", async () => {
|
||||
const session = createProcessSessionFixture({
|
||||
id: "sess-unknown-mode",
|
||||
command: "vim",
|
||||
backgrounded: true,
|
||||
cursorKeyMode: "unknown",
|
||||
});
|
||||
session.stdin = createWritableStdinStub();
|
||||
addSession(session);
|
||||
|
||||
const processTool = createProcessTool();
|
||||
const result = await processTool.execute("toolcall", {
|
||||
action: "send-keys",
|
||||
sessionId: "sess-unknown-mode",
|
||||
keys: ["up"],
|
||||
});
|
||||
|
||||
expect(result.details).toMatchObject({ status: "failed" });
|
||||
expect(result.content[0]).toMatchObject({
|
||||
type: "text",
|
||||
text: expect.stringContaining("cursor key mode is not known yet"),
|
||||
});
|
||||
});
|
||||
|
||||
test("process send-keys still sends non-cursor keys while mode is unknown", async () => {
|
||||
const session = createProcessSessionFixture({
|
||||
id: "sess-unknown-enter",
|
||||
command: "vim",
|
||||
backgrounded: true,
|
||||
cursorKeyMode: "unknown",
|
||||
});
|
||||
session.stdin = createWritableStdinStub();
|
||||
addSession(session);
|
||||
|
||||
const processTool = createProcessTool();
|
||||
const result = await processTool.execute("toolcall", {
|
||||
action: "send-keys",
|
||||
sessionId: "sess-unknown-enter",
|
||||
keys: ["Enter"],
|
||||
});
|
||||
|
||||
expect(result.details).toMatchObject({ status: "running" });
|
||||
});
|
||||
|
||||
@@ -16,9 +16,10 @@ import {
|
||||
setJobTtlMs,
|
||||
} from "./bash-process-registry.js";
|
||||
import { describeProcessTool } from "./bash-tools.descriptions.js";
|
||||
import { handleProcessSendKeys, type WritableStdin } from "./bash-tools.process-send-keys.js";
|
||||
import { deriveSessionName, pad, sliceLogLines, truncateMiddle } from "./bash-tools.shared.js";
|
||||
import { recordCommandPoll, resetCommandPollCount } from "./command-poll-backoff.js";
|
||||
import { encodeKeySequence, encodePaste, hasCursorModeSensitiveKeys } from "./pty-keys.js";
|
||||
import { encodePaste } from "./pty-keys.js";
|
||||
import { PROCESS_TOOL_DISPLAY_SUMMARY } from "./tool-description-presets.js";
|
||||
import type { AgentToolWithMeta } from "./tools/common.js";
|
||||
|
||||
@@ -28,11 +29,6 @@ export type ProcessToolDefaults = {
|
||||
scopeKey?: string;
|
||||
};
|
||||
|
||||
type WritableStdin = {
|
||||
write: (data: string, cb?: (err?: Error | null) => void) => void;
|
||||
end: () => void;
|
||||
destroyed?: boolean;
|
||||
};
|
||||
const DEFAULT_LOG_TAIL_LINES = 200;
|
||||
|
||||
function resolveLogSliceWindow(offset?: number, limit?: number) {
|
||||
@@ -480,38 +476,14 @@ export function createProcessTool(
|
||||
if (!resolved.ok) {
|
||||
return resolved.result;
|
||||
}
|
||||
const request = {
|
||||
return await handleProcessSendKeys({
|
||||
sessionId: params.sessionId,
|
||||
session: resolved.session,
|
||||
stdin: resolved.stdin,
|
||||
keys: params.keys,
|
||||
hex: params.hex,
|
||||
literal: params.literal,
|
||||
};
|
||||
if (resolved.session.cursorKeyMode === "unknown" && hasCursorModeSensitiveKeys(request)) {
|
||||
return failText(
|
||||
`Session ${params.sessionId} cursor key mode is not known yet. Poll or log until startup output appears, then retry send-keys.`,
|
||||
);
|
||||
}
|
||||
const cursorKeyMode =
|
||||
resolved.session.cursorKeyMode === "unknown"
|
||||
? undefined
|
||||
: resolved.session.cursorKeyMode;
|
||||
const { data, warnings } = encodeKeySequence(request, cursorKeyMode);
|
||||
if (!data) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "No key data provided.",
|
||||
},
|
||||
],
|
||||
details: { status: "failed" },
|
||||
};
|
||||
}
|
||||
await writeToStdin(resolved.stdin, data);
|
||||
return runningSessionResult(
|
||||
resolved.session,
|
||||
`Sent ${data.length} bytes to session ${params.sessionId}.` +
|
||||
(warnings.length ? `\nWarnings:\n- ${warnings.join("\n- ")}` : ""),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
case "submit": {
|
||||
|
||||
Reference in New Issue
Block a user