feat: pending-finish proposals and QQ image input
Proposal semantics: worker results no longer distinguish success/fail; only an explicit finish settles a pending proposal. Pending owner input follows up by resuming the original worker session. Dirty pending blocks start_next globally; clean pending only blocks its owner. QQ adapter now downloads image attachments and passes them as ACP image content blocks; video/file attachments degrade to link text.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { Readable, Writable } from "node:stream";
|
||||
import * as acp from "@agentclientprotocol/sdk";
|
||||
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gori-image-e2e-spike-"));
|
||||
const workspace = path.join(root, "workspace");
|
||||
await fs.mkdir(workspace, { recursive: true, mode: 0o700 });
|
||||
|
||||
const imageB64 = (await fs.readFile("/tmp/test7.b64", "utf8")).trim();
|
||||
|
||||
const child = spawn(process.env.KIMI_BIN || "kimi", ["acp"], {
|
||||
cwd: workspace,
|
||||
env: process.env,
|
||||
shell: false,
|
||||
stdio: ["pipe", "pipe", "pipe"]
|
||||
});
|
||||
let stderr = "";
|
||||
child.stderr.on("data", (chunk) => { stderr += chunk.toString("utf8"); });
|
||||
|
||||
let sessionId;
|
||||
const chunks = [];
|
||||
const app = acp.client({ name: "gori-image-e2e-spike", version: "0.1.0" })
|
||||
.onNotification(acp.methods.client.session.update, ({ params }) => {
|
||||
if (params.sessionId !== sessionId) return;
|
||||
if (params.update.sessionUpdate === "agent_message_chunk" && params.update.content.type === "text") {
|
||||
chunks.push(params.update.content.text);
|
||||
}
|
||||
});
|
||||
const stream = acp.ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
|
||||
const connection = app.connect(stream);
|
||||
|
||||
try {
|
||||
await connection.agent.request(acp.methods.agent.initialize, {
|
||||
protocolVersion: acp.PROTOCOL_VERSION,
|
||||
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false } },
|
||||
clientInfo: { name: "gori-image-e2e-spike", version: "0.1.0" }
|
||||
});
|
||||
const created = await connection.agent.request(acp.methods.agent.session.new, { cwd: workspace, mcpServers: [] });
|
||||
sessionId = created.sessionId;
|
||||
const result = await connection.agent.request(acp.methods.agent.session.prompt, {
|
||||
sessionId: created.sessionId,
|
||||
prompt: [
|
||||
{ type: "image", data: imageB64, mimeType: "image/png" },
|
||||
{ type: "text", text: "What number is drawn in this image? Answer with just the number." }
|
||||
]
|
||||
});
|
||||
console.log("stopReason:", result.stopReason);
|
||||
console.log("reply:", JSON.stringify(chunks.join("")));
|
||||
} catch (error) {
|
||||
console.error("PROMPT ERROR:", error?.message || error);
|
||||
if (stderr) console.error("STDERR tail:", stderr.slice(-800));
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
child.kill("SIGKILL");
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
}
|
||||
Reference in New Issue
Block a user