Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/electron/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@proma/electron",
"version": "0.17.45",
"version": "0.17.46",
"description": "Proma next gen ai software with general agents - Electron App",
"main": "dist/main.cjs",
"author": {
Expand Down
20 changes: 18 additions & 2 deletions apps/electron/src/main/lib/adapters/pi-utility-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ type CapabilityRequest = {
queryId: string
}

type QueryAbortResponse = {
accepted: boolean
completed?: boolean
}

type AsyncEventQueue<T> = {
push: (value: T) => void
end: () => void
Expand Down Expand Up @@ -97,17 +102,28 @@ export class PiUtilityAdapter {
abort(sessionId: string): void {
for (const pending of this.pendingQueries.values()) {
if (pending.sessionId !== sessionId) continue
void pending.client.call(
void pending.client.call<QueryAbortResponse>(
AGENT_RUNTIME_METHODS.QUERY_ABORT,
{ queryId: pending.queryId, sessionId },
{ queryId: pending.queryId, timeoutMs: 5_000 },
).catch((error) => {
).then((result) => {
if (result.completed !== false) return
this.failStoppedQuery(pending, new Error('停止 Agent 超时,已关闭卡住的运行时'))
}).catch((error) => {
console.warn(`[PiUtilityAdapter] abort failed: sessionId=${sessionId}`, error)
this.failStoppedQuery(pending, error)
})
return
}
}

private failStoppedQuery(pending: PendingQuery, error: unknown): void {
if (pending.ended || pending.runtimeFailed) return
pending.runtimeFailed = true
pending.queue.fail(error)
void pending.client.stop()
}

async sendQueuedMessage(
sessionId: string,
message: SDKUserMessageInput,
Expand Down
21 changes: 11 additions & 10 deletions apps/electron/src/renderer/components/agent/AgentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2348,15 +2348,16 @@ export function AgentView({ sessionId }: { sessionId: string }): React.ReactElem
})
}, [createBaseAdditionalDirectories, preparePendingFilesForSend, restoreQueuedAttachmentsToPending, sessionId, agentChannelId, agentModelId, agentChannelProvider, currentWorkspaceId, streaming, backgroundWaiting, suggestion, hasAvailableModel, store, consumeQuotedSelection, setStreamingStates, setAgentStreamErrors, setPromptSuggestions, setInputContent, setLiveMessagesMap, permissionMode, messagesLoaded, setQueuedMessages, setQuotedSelectionMap, sendPlainTextAgentMessage, isLegacyTranscript, isStopping])

/** 停止生成 */
/** 停止生成。异常流未发出终态时,允许再次下发幂等的 abort 请求。 */
const handleStop = React.useCallback((): void => {
if (isStopping) return
setIsStopping(true)
store.set(stoppedByUserSessionsAtom, (prev: Set<string>) => {
const next = new Set(prev)
next.add(sessionId)
return next
})
if (!isStopping) {
setIsStopping(true)
store.set(stoppedByUserSessionsAtom, (prev: Set<string>) => {
const next = new Set(prev)
next.add(sessionId)
return next
})
}

// 保持 running 到 STREAM_COMPLETE 到达。提前把它切成 false 会让输入框误以为
// 已经可以开启新 run,而底层 query 尚未退出,形成重复保存的竞态。
Expand Down Expand Up @@ -2942,13 +2943,13 @@ export function AgentView({ sessionId }: { sessionId: string }): React.ReactElem
size="icon"
className={inputToolbarDangerButtonClass}
onClick={handleStop}
disabled={isStopping}
aria-label={isStopping ? '再次停止 Agent' : '停止 Agent'}
>
<Square className="size-[16px]" fill="currentColor" strokeWidth={0} />
</Button>
</TooltipTrigger>
<TooltipContent side="top">
<p>停止 Agent ({getAcceleratorDisplay(getActiveAccelerator('stop-generation'))})</p>
<p>{isStopping ? '停止未确认,再次发送中断请求' : `停止 Agent (${getAcceleratorDisplay(getActiveAccelerator('stop-generation'))})`}</p>
</TooltipContent>
</Tooltip>
)
Expand Down
9 changes: 5 additions & 4 deletions apps/electron/src/utility/agent-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,12 +256,13 @@ async function handleQueryAbort(request: RuntimeRequest): Promise<void> {
respond(request, { accepted: false, reason: 'stale_or_inactive_query' })
return
}
const query = activeQuery
piAdapter.abort(sessionId)
await Promise.race([
activeQuery.done,
new Promise<void>((resolve) => setTimeout(resolve, 5_000)),
const completed = await Promise.race([
query.done.then(() => true),
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 5_000)),
])
respond(request, { accepted: true, queryId })
respond(request, { accepted: true, queryId, completed })
}

async function handleQueuedMessage(request: RuntimeRequest): Promise<void> {
Expand Down