fix(core): enforce agent-scoped ownership boundaries
This commit is contained in:
@@ -591,17 +591,27 @@ export function apply(ctx: Context, config: AcpConfig): void {
|
||||
return Promise.resolve()
|
||||
},
|
||||
|
||||
newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
|
||||
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
|
||||
assertOpen()
|
||||
validateWorkspaceParams(params)
|
||||
validateMcpServers(params)
|
||||
const sessionId = SessionId(randomUUID())
|
||||
const handle = agents.create({
|
||||
const handle = await agents.create({
|
||||
agentId: AgentId(sessionId),
|
||||
sessionId,
|
||||
meta: { cwd: params.cwd },
|
||||
agentOptions: agentOptions(config),
|
||||
})
|
||||
// Creation is now asynchronous because it awaits the unpublished setup
|
||||
// transaction. A client disconnect can therefore close this bridge
|
||||
// after the entry check but before the handle resolves; never install a
|
||||
// post-close record that quiesce() could not have seen.
|
||||
/* v8 ignore next 4 -- the in-memory transport rejects the in-flight RPC
|
||||
immediately on close; real stdio may let the handler resume */
|
||||
if (closed) {
|
||||
await handle.dispose()
|
||||
throw internalError('connection closed during session/new')
|
||||
}
|
||||
bySession.set(handle.agent, sessionId)
|
||||
sessions.set(sessionId, {
|
||||
sessionId,
|
||||
@@ -611,7 +621,7 @@ export function apply(ctx: Context, config: AcpConfig): void {
|
||||
terminalEnabled: terminalOutputCap,
|
||||
inflight: undefined,
|
||||
})
|
||||
return Promise.resolve({ sessionId })
|
||||
return { sessionId }
|
||||
},
|
||||
|
||||
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
|
||||
|
||||
@@ -229,10 +229,10 @@ describe('acp bridge — disposal & HMR safety', () => {
|
||||
// dispose one handle, and assert the other survives, registered and
|
||||
// queryable, with its session still in the store.
|
||||
const harness = await makeBridgeHarness({ storageDir, script: [] })
|
||||
const handleA = harness.ctx.agents.create({
|
||||
const handleA = await harness.ctx.agents.create({
|
||||
agentId: AgentId('sib-a'), sessionId: SessionId('sib-a'), agentOptions: { model: 'mock' },
|
||||
})
|
||||
const handleB = harness.ctx.agents.create({
|
||||
const handleB = await harness.ctx.agents.create({
|
||||
agentId: AgentId('sib-b'), sessionId: SessionId('sib-b'), agentOptions: { model: 'mock' },
|
||||
})
|
||||
expect(harness.ctx.agents.get(AgentId('sib-a'))).toBe(handleA.agent)
|
||||
@@ -261,7 +261,7 @@ describe('acp bridge — disposal & HMR safety', () => {
|
||||
// a clean turn, dispose, and assert the session was STILL removed.
|
||||
const harness = await makeBridgeHarness({ storageDir, script: [textResponse('ok')] })
|
||||
harness.ctx.on('agent/disposed', () => { throw new Error('boom disposed listener') })
|
||||
const handle = harness.ctx.agents.create({
|
||||
const handle = await harness.ctx.agents.create({
|
||||
agentId: AgentId('guard-a'), sessionId: SessionId('guard-a'), agentOptions: { model: 'mock' },
|
||||
})
|
||||
handle.agent.send([{ type: 'text', text: 'go' }])
|
||||
@@ -282,7 +282,7 @@ describe('acp bridge — disposal & HMR safety', () => {
|
||||
// first call's await agent.done + final flush finished. Every caller must
|
||||
// observe the same quiescence boundary.
|
||||
const harness = await makeBridgeHarness({ storageDir, script: ['hang'] })
|
||||
const handle = harness.ctx.agents.create({
|
||||
const handle = await harness.ctx.agents.create({
|
||||
agentId: AgentId('conc-a'), sessionId: SessionId('conc-a'), agentOptions: { model: 'mock' },
|
||||
})
|
||||
// Drive a turn that hangs in the model stream, so the loop is mid-turn when
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('acp bridge — demux & config edges', () => {
|
||||
await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] })
|
||||
const before = harness.updates.length
|
||||
|
||||
const { agent: foreign } = harness.ctx.agents.create({ agentId: AgentId('foreign'), sessionId: SessionId('foreign-session'), agentOptions: { model: 'mock' } })
|
||||
const { agent: foreign } = await harness.ctx.agents.create({ agentId: AgentId('foreign'), sessionId: SessionId('foreign-session'), agentOptions: { model: 'mock' } })
|
||||
foreign.send([{ type: 'text', text: 'hi' }])
|
||||
await foreign.whenIdle()
|
||||
await new Promise(r => setTimeout(r, 10))
|
||||
|
||||
Reference in New Issue
Block a user