feat: add process submit helper
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
- Tools: Firecrawl fallback now uses bot-circumvention + cache by default; remove basic HTML fallback when extraction fails.
|
||||
- Tools: default `exec` exit notifications and auto-migrate legacy `tools.bash` to `tools.exec`.
|
||||
- Tools: add tmux-style `process send-keys` and bracketed paste helpers for PTY sessions.
|
||||
- Tools: add `process submit` helper to send CR for PTY sessions.
|
||||
- Status: trim `/status` to current-provider usage only and drop the OAuth/token block.
|
||||
- Directory: unify `clawdbot directory` across channels and plugin channels.
|
||||
- UI: allow deleting sessions from the Control UI.
|
||||
|
||||
@@ -46,6 +46,11 @@ Send keys (tmux-style):
|
||||
{"tool":"process","action":"send-keys","sessionId":"<id>","keys":["Up","Up","Enter"]}
|
||||
```
|
||||
|
||||
Submit (send CR only):
|
||||
```json
|
||||
{"tool":"process","action":"submit","sessionId":"<id>"}
|
||||
```
|
||||
|
||||
Paste (bracketed by default):
|
||||
```json
|
||||
{"tool":"process","action":"paste","sessionId":"<id>","text":"line1\nline2\n"}
|
||||
|
||||
@@ -44,3 +44,36 @@ test("process send-keys encodes Enter for pty sessions", async () => {
|
||||
|
||||
throw new Error("PTY session did not exit after send-keys");
|
||||
});
|
||||
|
||||
test("process submit sends CR for pty sessions", async () => {
|
||||
const execTool = createExecTool();
|
||||
const processTool = createProcessTool();
|
||||
const result = await execTool.execute("toolcall", {
|
||||
command:
|
||||
"node -e \"process.stdin.on('data', d => { if (d.includes(13)) { process.stdout.write('submitted'); process.exit(0); } });\"",
|
||||
pty: true,
|
||||
background: true,
|
||||
});
|
||||
|
||||
expect(result.details.status).toBe("running");
|
||||
const sessionId = result.details.sessionId;
|
||||
expect(sessionId).toBeTruthy();
|
||||
|
||||
await processTool.execute("toolcall", {
|
||||
action: "submit",
|
||||
sessionId,
|
||||
});
|
||||
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
await wait(50);
|
||||
const poll = await processTool.execute("toolcall", { action: "poll", sessionId });
|
||||
const details = poll.details as { status?: string; aggregated?: string };
|
||||
if (details.status !== "running") {
|
||||
expect(details.status).toBe("completed");
|
||||
expect(details.aggregated ?? "").toContain("submitted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("PTY session did not exit after submit");
|
||||
});
|
||||
|
||||
@@ -58,11 +58,22 @@ export function createProcessTool(
|
||||
return {
|
||||
name: "process",
|
||||
label: "process",
|
||||
description: "Manage running exec sessions: list, poll, log, write, send-keys, paste, kill.",
|
||||
description:
|
||||
"Manage running exec sessions: list, poll, log, write, send-keys, submit, paste, kill.",
|
||||
parameters: processSchema,
|
||||
execute: async (_toolCallId, args) => {
|
||||
const params = args as {
|
||||
action: "list" | "poll" | "log" | "write" | "send-keys" | "paste" | "kill" | "clear" | "remove";
|
||||
action:
|
||||
| "list"
|
||||
| "poll"
|
||||
| "log"
|
||||
| "write"
|
||||
| "send-keys"
|
||||
| "submit"
|
||||
| "paste"
|
||||
| "kill"
|
||||
| "clear"
|
||||
| "remove";
|
||||
sessionId?: string;
|
||||
data?: string;
|
||||
keys?: string[];
|
||||
@@ -429,6 +440,62 @@ export function createProcessTool(
|
||||
};
|
||||
}
|
||||
|
||||
case "submit": {
|
||||
if (!scopedSession) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `No active session found for ${params.sessionId}`,
|
||||
},
|
||||
],
|
||||
details: { status: "failed" },
|
||||
};
|
||||
}
|
||||
if (!scopedSession.backgrounded) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Session ${params.sessionId} is not backgrounded.`,
|
||||
},
|
||||
],
|
||||
details: { status: "failed" },
|
||||
};
|
||||
}
|
||||
const stdin = scopedSession.stdin ?? scopedSession.child?.stdin;
|
||||
if (!stdin || stdin.destroyed) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Session ${params.sessionId} stdin is not writable.`,
|
||||
},
|
||||
],
|
||||
details: { status: "failed" },
|
||||
};
|
||||
}
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
stdin.write("\r", (err) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Submitted session ${params.sessionId} (sent CR).`,
|
||||
},
|
||||
],
|
||||
details: {
|
||||
status: "running",
|
||||
sessionId: params.sessionId,
|
||||
name: scopedSession ? deriveSessionName(scopedSession.command) : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case "paste": {
|
||||
if (!scopedSession) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user