Files
goodbuddy/src/main/agent/remote-runtime-channel.ts
T
mesalogo f8d07419d2 feat: generate and edit conversation images across runtimes
Add model-controlled image invocation and read-only runtime assignments. Route image tools through Main so accepted operations retain their originating conversation when chat runtimes change, with saved references, cancellation, and manual recovery.

Keep Node hashing outside browser-shared contracts to restore production builds. Validation includes the production build, typecheck, lint, focused regressions, real Host transport checks, and two successful real image requests. Full UI-driven runtime switching acceptance remains documented as pending.

Release note: 开启图片模型的会话调用后,聊天助手可调用图片生成与编辑工具,Runtime 分配自动管理。图片成果保存在原会话,可继续修改,并独立显示生成状态和取消入口。
2026-09-15 15:51:52 +08:00

198 lines
6.2 KiB
TypeScript

import type {
AcpBindingCursors,
AcpCompletePromptRequest,
AcpCompletePromptResult,
AcpEscalateCancellationRequest,
AcpReconcilePromptRequest,
AcpReconcilePromptResult
} from '../../shared/agent-protocol'
import type {
RemoteOwnedPromptStartRequest,
RemoteOwnedPromptStartResult,
RemotePromptOperationAcceptance,
RemotePromptOperationPreparation,
remoteOwnedPromptAttachRequestSchema,
remoteSemanticTranscriptAckRequestSchema,
remoteSemanticTranscriptAckResultSchema,
remoteSemanticTranscriptPageRequestSchema,
remoteSemanticTranscriptPageResultSchema,
} from '../../shared/remote-agent-contracts'
import type { z } from 'zod'
import type { ImageToolBinding } from './image-tool-binding'
import type { RemoteQuestionResponse } from '../../shared/remote-question-contracts'
import type {
ModelBridgePolicy
} from '../../shared/model-bridge-contracts'
export type RemoteRuntimeChannelCapabilities = {
cancellationEscalation: boolean
promptOperationReconciliation: boolean
ownedPrompt?: boolean
modelBridge?: boolean
}
export type RemoteOwnedPromptAttachRequest = z.infer<
typeof remoteOwnedPromptAttachRequestSchema
>
export type RemoteSemanticTranscriptPageRequest = z.infer<
typeof remoteSemanticTranscriptPageRequestSchema
>
export type RemoteSemanticTranscriptPageResult = z.infer<
typeof remoteSemanticTranscriptPageResultSchema
>
export type RemoteSemanticTranscriptAckRequest = z.infer<
typeof remoteSemanticTranscriptAckRequestSchema
>
export type RemoteSemanticTranscriptAckResult = z.infer<
typeof remoteSemanticTranscriptAckResultSchema
>
export type RuntimeSessionBindingCursors = AcpBindingCursors
export type RemoteCancellationEscalation = Omit<
AcpEscalateCancellationRequest,
'reason'
> & {
reason: unknown
}
export type RemotePromptOperationReconciliation =
AcpReconcilePromptRequest
export type RemotePromptOperationReconciliationResult =
AcpReconcilePromptResult
export type RemotePromptOperationCompletion = AcpCompletePromptRequest
export type RemotePromptOperationCompletionResult =
AcpCompletePromptResult
export type RemoteModelBridgeOpenRequest = {
bindingId: string
promptOperationId: string
requestId: string
policy: ModelBridgePolicy
}
export type RemoteModelBridgeCloseResult = {
clean: boolean
poisoned: boolean
}
export interface RemoteModelBridgeSession {
readonly version: 'goodbuddy-model-bridge-v1'
readonly channelId: string
readonly channelEpoch: string
readonly policy: ModelBridgePolicy
readonly closed: Promise<RemoteModelBridgeCloseResult>
close(reason?: string): Promise<RemoteModelBridgeCloseResult>
}
/**
* A process-neutral, already authenticated ACP virtual channel.
*
* Implementations own framing, journaling and transport generation checks.
* The runtime intentionally receives no SSH client, socket or command API.
*/
export interface RemoteRuntimeChannel {
respondToQuestion?(request: RemoteQuestionResponse): Promise<void>
readonly input: ReadableStream<Uint8Array>
readonly output: WritableStream<Uint8Array>
readonly generation: number
readonly channelEpoch: string
/**
* Signed Runtime capability identity advertised by the Agent before the
* untrusted Runtime process starts.
*/
readonly advertisedAcpCapabilitiesDigest: string
readonly capabilities: RemoteRuntimeChannelCapabilities
readonly closed: Promise<void>
isCurrentGeneration(): boolean
getBindingCursors(
bindingId: string
): Promise<RuntimeSessionBindingCursors>
/** Accepts the prompt and starts its Ask or Execute Runtime process. */
preparePrompt(
preparation: RemotePromptOperationPreparation,
imageToolBinding?: ImageToolBinding,
waitSignal?: AbortSignal
): Promise<RemotePromptOperationAcceptance>
/** Starts an Agent-owned ACP prompt exactly once for its stable operation. */
startOwnedPrompt?(
request: RemoteOwnedPromptStartRequest
): Promise<RemoteOwnedPromptStartResult>
/** Reattaches to an already accepted Agent-owned prompt without replaying it. */
attachOwnedPrompt?(
request: RemoteOwnedPromptAttachRequest
): Promise<RemoteOwnedPromptStartResult>
pageOwnedPromptTranscript?(
request: RemoteSemanticTranscriptPageRequest
): Promise<RemoteSemanticTranscriptPageResult>
ackOwnedPromptTranscript?(
request: RemoteSemanticTranscriptAckRequest
): Promise<RemoteSemanticTranscriptAckResult>
/**
* Bounds same-daemon stream recovery to the signed prompt lifetime and
* caller cancellation. Implementations that cannot recover may omit it.
*/
setRecoveryBoundary?(
deadlineAt: string,
signal: AbortSignal
): void
clearRecoveryBoundary?(): void
/**
* Allocates and registers a fresh Main-owned model channel for one prompt.
* Legacy channels may omit this when the runtime is configured without a
* model bridge.
*/
openModelBridge?(
request: RemoteModelBridgeOpenRequest
): Promise<RemoteModelBridgeSession>
completePromptOperation(
operation: RemotePromptOperationCompletion
): Promise<RemotePromptOperationCompletionResult>
/**
* Stops or resumes inbound ACP delivery. Pausing must take effect before
* the returned promise resolves and must not advance the Main ACK cursor.
*/
setInboundPaused(paused: boolean): Promise<void>
escalateCancellation(
escalation: RemoteCancellationEscalation
): Promise<void>
reconcilePromptOperation(
operation: RemotePromptOperationReconciliation
): Promise<RemotePromptOperationReconciliationResult>
close(): Promise<void>
}
export class StaleRemoteRuntimeGenerationError extends Error {
constructor() {
super('远端 Runtime 连接代际已失效')
this.name = 'StaleRemoteRuntimeGenerationError'
}
}
export function isDefinitiveRemoteRuntimeRequestRejection(
error: unknown,
method: string
): boolean {
if (
error === null ||
typeof error !== 'object' ||
!('remoteRequestOutcome' in error) ||
error.remoteRequestOutcome !== 'rejected' ||
!('remoteMethod' in error) ||
error.remoteMethod !== method
) {
return false
}
return true
}
export function assertCurrentRemoteRuntimeGeneration(
channel: RemoteRuntimeChannel
): void {
if (!channel.isCurrentGeneration()) {
throw new StaleRemoteRuntimeGenerationError()
}
}