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:
zenord
2026-08-19 13:36:23 +08:00
parent 33854721bf
commit 7aa610294c
24 changed files with 1285 additions and 391 deletions
+5 -3
View File
@@ -4,7 +4,7 @@ import fs from "node:fs";
import type { AcpConfig, PermissionPolicy } from "../config.js";
import type { WorkerProcessGroup } from "../core/proposal-store.js";
import type { ResolvedBot } from "../roles/role-registry.js";
import { AcpClient, type SafeActivityCategory } from "./client.js";
import { AcpClient, type AcpPromptContent, type SafeActivityCategory } from "./client.js";
export class AcpSessionRestoreError extends Error {}
@@ -35,6 +35,7 @@ export class AcpWorker {
nativeSessionId?: string;
processGroup?: WorkerProcessGroup;
isolationViolated = false;
supportsImages = false;
lastUsedAt = Date.now();
turnStartedAt?: number;
lastActivityAt?: number;
@@ -94,6 +95,7 @@ export class AcpWorker {
this.processGroup = { pgid: this.child.pid, token: processToken };
await this.options.onSpawn?.(this.processGroup);
const initialized = await this.client.initialize();
this.supportsImages = this.client.supportsImageInput();
if (this.kind === "assistant" && !this.options.allowUnverifiedAssistantAgent && initialized.agentInfo?.name !== "Kimi Code CLI") {
throw new Error("Assistant sessions require Kimi Code ACP with execution-layer no-tool profiles");
}
@@ -111,7 +113,7 @@ export class AcpWorker {
}
}
async prompt(text: string, phase: Exclude<AcpWorkerPhase, "idle"> = "processing"): Promise<string> {
async prompt(input: string | AcpPromptContent[], phase: Exclude<AcpWorkerPhase, "idle"> = "processing"): Promise<string> {
if (!this.client || !this.nativeSessionId || this.exited) throw new Error("ACP worker is not available");
if (this.inFlight) throw new Error("ACP worker already has an in-flight turn");
const now = Date.now();
@@ -131,7 +133,7 @@ export class AcpWorker {
}, this.config.promptTimeoutMs);
});
try {
const result = await Promise.race([this.client.prompt(text, this.abort.signal), timeoutPromise]);
const result = await Promise.race([this.client.prompt(input, this.abort.signal), timeoutPromise]);
if (this.kind === "assistant") await this.client.settleIsolation();
return result;
} finally {