fix: serve 模式下权限事件被 shouldAvoidPermissionPrompts 吞没导致直接拒绝
- 当 --permission-prompt-tool 指定时不设置 shouldAvoidPermissionPrompts,允许 can_use_tool 控制消息正常发出 - serve 默认权限模式从 acceptEdits 改为 default,确保权限检查链路完整
This commit is contained in:
parent
7c2d9654f4
commit
fd509086f3
12429
src/main.tsx
12429
src/main.tsx
File diff suppressed because it is too large
Load Diff
|
|
@ -3,10 +3,24 @@ import { createInterface } from 'readline'
|
||||||
import { jsonParse, jsonStringify } from '../utils/slowOperations.js'
|
import { jsonParse, jsonStringify } from '../utils/slowOperations.js'
|
||||||
import { logError } from '../utils/log.js'
|
import { logError } from '../utils/log.js'
|
||||||
import type { EventBus } from './eventBus.js'
|
import type { EventBus } from './eventBus.js'
|
||||||
import { INIT_TIMEOUT_MS, getScriptArgsForChild, loadChildSpawnPrefix } from './childSpawn.js'
|
import {
|
||||||
|
INIT_TIMEOUT_MS,
|
||||||
|
getScriptArgsForChild,
|
||||||
|
loadChildSpawnPrefix,
|
||||||
|
} from './childSpawn.js'
|
||||||
import { ControlChannel } from './sessionControlChannel.js'
|
import { ControlChannel } from './sessionControlChannel.js'
|
||||||
import { routeMessage, type StdoutMessage, type MessageRouterCtx } from './sessionMessageRouter.js'
|
import {
|
||||||
import type { SessionBusyStatus, SessionState, InitData, PendingPermission, PendingQuestion } from './types.js'
|
routeMessage,
|
||||||
|
type StdoutMessage,
|
||||||
|
type MessageRouterCtx,
|
||||||
|
} from './sessionMessageRouter.js'
|
||||||
|
import type {
|
||||||
|
SessionBusyStatus,
|
||||||
|
SessionState,
|
||||||
|
InitData,
|
||||||
|
PendingPermission,
|
||||||
|
PendingQuestion,
|
||||||
|
} from './types.js'
|
||||||
import type { DisposableChildProcess } from '../utils/killOnDrop.js'
|
import type { DisposableChildProcess } from '../utils/killOnDrop.js'
|
||||||
import type { SessionMessage } from './transcriptReader.js'
|
import type { SessionMessage } from './transcriptReader.js'
|
||||||
|
|
||||||
|
|
@ -169,13 +183,15 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
this.eventBus = opts.eventBus
|
this.eventBus = opts.eventBus
|
||||||
this._model = opts.model
|
this._model = opts.model
|
||||||
this._agent = opts.agent
|
this._agent = opts.agent
|
||||||
this._permissionMode = opts.permissionMode ?? 'acceptEdits'
|
this._permissionMode = opts.permissionMode ?? 'default'
|
||||||
this.verbose = opts.verbose ?? false
|
this.verbose = opts.verbose ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(listener: MessageListener): () => void {
|
onMessage(listener: MessageListener): () => void {
|
||||||
this.listeners.add(listener)
|
this.listeners.add(listener)
|
||||||
return () => { this.listeners.delete(listener) }
|
return () => {
|
||||||
|
this.listeners.delete(listener)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn(): void {
|
spawn(): void {
|
||||||
|
|
@ -214,7 +230,12 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
const saved = loadChildSpawnPrefix()
|
const saved = loadChildSpawnPrefix()
|
||||||
const defineArgs = saved?.defineArgs ?? []
|
const defineArgs = saved?.defineArgs ?? []
|
||||||
const featureArgs = saved?.featureArgs ?? []
|
const featureArgs = saved?.featureArgs ?? []
|
||||||
const spawnArgs = [...defineArgs, ...featureArgs, ...this.opts.scriptArgs, ...printArgs.filter((x): x is string => x != null)]
|
const spawnArgs = [
|
||||||
|
...defineArgs,
|
||||||
|
...featureArgs,
|
||||||
|
...this.opts.scriptArgs,
|
||||||
|
...printArgs.filter((x): x is string => x != null),
|
||||||
|
]
|
||||||
|
|
||||||
this._spawnCwd = this.opts.cwd
|
this._spawnCwd = this.opts.cwd
|
||||||
this.child = spawn(this.opts.execPath, spawnArgs, {
|
this.child = spawn(this.opts.execPath, spawnArgs, {
|
||||||
|
|
@ -244,7 +265,9 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
const reject = this.initReject
|
const reject = this.initReject
|
||||||
this.initResolve = null
|
this.initResolve = null
|
||||||
this.initReject = null
|
this.initReject = null
|
||||||
process.stderr.write(`[serve:${this.sessionId}] exit_code=${code} cwd=${this.cwd} stderr:\n${this.lastStderr.slice(-5).join('\n')}\n`)
|
process.stderr.write(
|
||||||
|
`[serve:${this.sessionId}] exit_code=${code} cwd=${this.cwd} stderr:\n${this.lastStderr.slice(-5).join('\n')}\n`,
|
||||||
|
)
|
||||||
reject(new Error(`Process exited with code ${code}`))
|
reject(new Error(`Process exited with code ${code}`))
|
||||||
}
|
}
|
||||||
if (this.promptReject) {
|
if (this.promptReject) {
|
||||||
|
|
@ -363,7 +386,10 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
this.eventBus.publishSessionEvent(this.sessionId, event, data)
|
this.eventBus.publishSessionEvent(this.sessionId, event, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitOpencodeEvent(event: string, properties: Record<string, unknown>): void {
|
private emitOpencodeEvent(
|
||||||
|
event: string,
|
||||||
|
properties: Record<string, unknown>,
|
||||||
|
): void {
|
||||||
if (this.opts.silent) return
|
if (this.opts.silent) return
|
||||||
if (properties.type === event && 'properties' in properties) {
|
if (properties.type === event && 'properties' in properties) {
|
||||||
this.eventBus.publish(event, properties as Record<string, unknown>)
|
this.eventBus.publish(event, properties as Record<string, unknown>)
|
||||||
|
|
@ -410,27 +436,47 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
getProviderId: () => this._providerId,
|
getProviderId: () => this._providerId,
|
||||||
getInitRequestId: () => this.initRequestId,
|
getInitRequestId: () => this.initRequestId,
|
||||||
getLastMessageUuid: () => this._lastMessageUuid,
|
getLastMessageUuid: () => this._lastMessageUuid,
|
||||||
setStatus: (s) => { this._status = s },
|
setStatus: s => {
|
||||||
setBusyStatus: (s) => { this._busyStatus = s },
|
this._status = s
|
||||||
setModel: (m) => { this._model = m },
|
},
|
||||||
setProviderId: (id) => { this._providerId = id },
|
setBusyStatus: s => {
|
||||||
setInitData: (d) => { this._initData = d },
|
this._busyStatus = s
|
||||||
setLastMessageUuid: (u) => { this._lastMessageUuid = u },
|
},
|
||||||
setLastActiveAt: (t) => { this.lastActiveAt = t },
|
setModel: m => {
|
||||||
addCost: (c) => { this._costUsd += c },
|
this._model = m
|
||||||
addInputTokens: (t) => { this._inputTokens += t },
|
},
|
||||||
addOutputTokens: (t) => { this._outputTokens += t },
|
setProviderId: id => {
|
||||||
|
this._providerId = id
|
||||||
|
},
|
||||||
|
setInitData: d => {
|
||||||
|
this._initData = d
|
||||||
|
},
|
||||||
|
setLastMessageUuid: u => {
|
||||||
|
this._lastMessageUuid = u
|
||||||
|
},
|
||||||
|
setLastActiveAt: t => {
|
||||||
|
this.lastActiveAt = t
|
||||||
|
},
|
||||||
|
addCost: c => {
|
||||||
|
this._costUsd += c
|
||||||
|
},
|
||||||
|
addInputTokens: t => {
|
||||||
|
this._inputTokens += t
|
||||||
|
},
|
||||||
|
addOutputTokens: t => {
|
||||||
|
this._outputTokens += t
|
||||||
|
},
|
||||||
getControlChannel: () => this._controlChannel,
|
getControlChannel: () => this._controlChannel,
|
||||||
getPendingPermissions: () => this.pendingPermissions,
|
getPendingPermissions: () => this.pendingPermissions,
|
||||||
getPendingQuestions: () => this.pendingQuestions,
|
getPendingQuestions: () => this.pendingQuestions,
|
||||||
resolveInit: (data) => {
|
resolveInit: data => {
|
||||||
const resolve = this.initResolve
|
const resolve = this.initResolve
|
||||||
this.initResolve = null
|
this.initResolve = null
|
||||||
this.initReject = null
|
this.initReject = null
|
||||||
resolve?.(data)
|
resolve?.(data)
|
||||||
this.opts.onInit?.(data)
|
this.opts.onInit?.(data)
|
||||||
},
|
},
|
||||||
resolvePrompt: (value) => {
|
resolvePrompt: value => {
|
||||||
this._busyStatus = { type: 'idle' }
|
this._busyStatus = { type: 'idle' }
|
||||||
this.emitBusyStatus()
|
this.emitBusyStatus()
|
||||||
if (this.promptResolve) {
|
if (this.promptResolve) {
|
||||||
|
|
@ -442,22 +488,28 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
this._titleGenerationAttempted = true
|
this._titleGenerationAttempted = true
|
||||||
const content = this._firstPromptContent
|
const content = this._firstPromptContent
|
||||||
void this.sendControlRequest(
|
void this.sendControlRequest(
|
||||||
{ subtype: 'generate_session_title', description: content, persist: true },
|
{
|
||||||
|
subtype: 'generate_session_title',
|
||||||
|
description: content,
|
||||||
|
persist: true,
|
||||||
|
},
|
||||||
30_000,
|
30_000,
|
||||||
).then((res) => {
|
)
|
||||||
const title = (res.title as string) || content.slice(0, 100)
|
.then(res => {
|
||||||
this.setTitle(title)
|
const title = (res.title as string) || content.slice(0, 100)
|
||||||
}).catch(() => {
|
this.setTitle(title)
|
||||||
this.setTitle(content.slice(0, 100))
|
})
|
||||||
})
|
.catch(() => {
|
||||||
|
this.setTitle(content.slice(0, 100))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emitEvent: (event, data) => this.emitEvent(event, data),
|
emitEvent: (event, data) => this.emitEvent(event, data),
|
||||||
emitOpencodeEvent: (event, props) => this.emitOpencodeEvent(event, props),
|
emitOpencodeEvent: (event, props) => this.emitOpencodeEvent(event, props),
|
||||||
emitBusyStatus: () => this.emitBusyStatus(),
|
emitBusyStatus: () => this.emitBusyStatus(),
|
||||||
emitMessage: (msg) => this.emitMessage(msg),
|
emitMessage: msg => this.emitMessage(msg),
|
||||||
writeStdin: (data) => this.writeStdin(data),
|
writeStdin: data => this.writeStdin(data),
|
||||||
pushBufferMessage: (msg) => this.pushMessage(msg),
|
pushBufferMessage: msg => this.pushMessage(msg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -501,11 +553,12 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
return (response.mcpServers as unknown[]) ?? []
|
return (response.mcpServers as unknown[]) ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
async prompt(content: string, opts?: { parts?: Array<Record<string, unknown>>; messageID?: string }): Promise<{ done: boolean; error?: string }> {
|
async prompt(
|
||||||
|
content: string,
|
||||||
|
opts?: { parts?: Array<Record<string, unknown>>; messageID?: string },
|
||||||
|
): Promise<{ done: boolean; error?: string }> {
|
||||||
if (this._status === 'stopped') {
|
if (this._status === 'stopped') {
|
||||||
throw new Error(
|
throw new Error(`Session ${this.sessionId} is stopped`)
|
||||||
`Session ${this.sessionId} is stopped`,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (this._prompting) {
|
if (this._prompting) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -558,11 +611,11 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.promptResolve = (value) => {
|
this.promptResolve = value => {
|
||||||
this._prompting = false
|
this._prompting = false
|
||||||
resolve(value)
|
resolve(value)
|
||||||
}
|
}
|
||||||
this.promptReject = (reason) => {
|
this.promptReject = reason => {
|
||||||
this._prompting = false
|
this._prompting = false
|
||||||
reject(reason)
|
reject(reason)
|
||||||
}
|
}
|
||||||
|
|
@ -590,7 +643,7 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
resolve()
|
resolve()
|
||||||
}, 2000)
|
}, 2000)
|
||||||
const originalResolve = this.promptResolve
|
const originalResolve = this.promptResolve
|
||||||
this.promptResolve = (value) => {
|
this.promptResolve = value => {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
originalResolve?.(value)
|
originalResolve?.(value)
|
||||||
resolve()
|
resolve()
|
||||||
|
|
@ -642,7 +695,8 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
behavior,
|
behavior,
|
||||||
})
|
})
|
||||||
if (toolName === 'AskUserQuestion') {
|
if (toolName === 'AskUserQuestion') {
|
||||||
const eventType = behavior === 'allow' ? 'question.replied' : 'question.rejected'
|
const eventType =
|
||||||
|
behavior === 'allow' ? 'question.replied' : 'question.rejected'
|
||||||
this.emitOpencodeEvent(eventType, {
|
this.emitOpencodeEvent(eventType, {
|
||||||
sessionID: this.sessionId,
|
sessionID: this.sessionId,
|
||||||
requestID: requestId,
|
requestID: requestId,
|
||||||
|
|
@ -677,7 +731,8 @@ export class SessionHandle implements DisposableChildProcess {
|
||||||
request_id: requestId,
|
request_id: requestId,
|
||||||
action,
|
action,
|
||||||
})
|
})
|
||||||
const eventType = action === 'accept' ? 'question.replied' : 'question.rejected'
|
const eventType =
|
||||||
|
action === 'accept' ? 'question.replied' : 'question.rejected'
|
||||||
this.emitOpencodeEvent(eventType, {
|
this.emitOpencodeEvent(eventType, {
|
||||||
sessionID: this.sessionId,
|
sessionID: this.sessionId,
|
||||||
requestID: requestId,
|
requestID: requestId,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user