mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
Merge ACP cancellation simplification into DSH SDK layer
This commit is contained in:
@@ -491,11 +491,13 @@ export async function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpe
|
||||
// A child closing its protocol stream can precede whole-tree exit
|
||||
// observation. Local cancellation does not need the discarded startup
|
||||
// classification; other failures use the configured process grace.
|
||||
const startupOutcome = cancelledBeforeCleanup
|
||||
? processOutcome
|
||||
: await observeProcessOutcome()
|
||||
const failure = startupFailure(error, startupStage, child, startupOutcome)
|
||||
if (cancelledBeforeCleanup) {
|
||||
const startup = cancelledBeforeCleanup
|
||||
? { kind: 'cancelled' } as const
|
||||
: {
|
||||
kind: 'failed',
|
||||
failure: startupFailure(error, startupStage, child, await observeProcessOutcome()),
|
||||
} as const
|
||||
if (startup.kind === 'cancelled') {
|
||||
// Local cancellation owns the startup outcome; only cleanup failure is
|
||||
// reported below when teardown itself rejects.
|
||||
} else {
|
||||
@@ -512,18 +514,18 @@ export async function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpe
|
||||
category: processOutcome === undefined ? 'unknown' : 'process-exit',
|
||||
...(processOutcome === undefined ? {} : { outcome: processOutcome }),
|
||||
}, cleanupError)
|
||||
if (cancelledBeforeCleanup) {
|
||||
if (startup.kind === 'cancelled') {
|
||||
throw new AggregateError([cleanupFailure], cleanupFailure.message)
|
||||
}
|
||||
throw new AggregateError(
|
||||
[failure, cleanupFailure],
|
||||
`${failure.message}; ${cleanupFailure.message}`,
|
||||
[startup.failure, cleanupFailure],
|
||||
`${startup.failure.message}; ${cleanupFailure.message}`,
|
||||
)
|
||||
}
|
||||
if (cancelledBeforeCleanup) {
|
||||
if (startup.kind === 'cancelled') {
|
||||
throw new Error('subagent request was aborted before the ACP child started')
|
||||
}
|
||||
throw failure
|
||||
throw startup.failure
|
||||
}
|
||||
// The startup transaction validates the returned id before it can fulfill.
|
||||
// This assertion carries that cross-closure invariant into TypeScript.
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
* provider's fixed permission fact.
|
||||
* - `MOCK_CRASH_ON_INITIALIZE` — exit while the unpublished initialize
|
||||
* operation is active.
|
||||
* - `MOCK_CLOSE_PROTOCOL_ON_INITIALIZE` — close stdout while keeping the
|
||||
* process alive, producing initialize-stage transport.
|
||||
* - `MOCK_CLOSE_PROTOCOL_ON_PROMPT` — close stdout while keeping the process
|
||||
* alive, producing a prompt-stage transport failure.
|
||||
* - `MOCK_CRASH_AFTER_CHUNK` — exit after streaming the assistant chunk, so
|
||||
@@ -94,6 +96,7 @@ const IGNORE_PERMISSION_DECISION = process.env.MOCK_PERMISSION_IGNORE_DECISION =
|
||||
const NO_ALLOW = process.env.MOCK_NO_ALLOW === '1'
|
||||
const THOUGHT = process.env.MOCK_THOUGHT === '1'
|
||||
const CRASH_ON_INITIALIZE = process.env.MOCK_CRASH_ON_INITIALIZE === '1'
|
||||
const CLOSE_PROTOCOL_ON_INITIALIZE = process.env.MOCK_CLOSE_PROTOCOL_ON_INITIALIZE === '1'
|
||||
const CRASH_ON_CANCEL = process.env.MOCK_CRASH_ON_CANCEL === '1'
|
||||
const CRASH_ON_PROMPT = process.env.MOCK_CRASH_ON_PROMPT === '1'
|
||||
const CLOSE_PROTOCOL_ON_PROMPT = process.env.MOCK_CLOSE_PROTOCOL_ON_PROMPT === '1'
|
||||
@@ -118,6 +121,11 @@ function makeAgent(conn: AgentSideConnection): Agent {
|
||||
return {
|
||||
initialize(_params: InitializeRequest): Promise<InitializeResponse> {
|
||||
if (CRASH_ON_INITIALIZE) process.exit(11)
|
||||
if (CLOSE_PROTOCOL_ON_INITIALIZE) {
|
||||
process.stdout.end()
|
||||
setInterval(() => { /* keep the process alive after protocol EOF */ }, 1000)
|
||||
return new Promise<InitializeResponse>(() => {})
|
||||
}
|
||||
return Promise.resolve({
|
||||
protocolVersion: PROTOCOL_VERSION,
|
||||
agentCapabilities: { loadSession: false, promptCapabilities: { image: false, audio: false, embeddedContext: false } },
|
||||
|
||||
@@ -573,6 +573,23 @@ describe('dsh-subagent-acp', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('reports initialize-stage transport when the child closes the protocol but stays alive', async () => {
|
||||
const error = await startAcpRun(request(), {
|
||||
command: process.execPath,
|
||||
args: [mockServer],
|
||||
cwd: process.cwd(),
|
||||
permission: 'reject',
|
||||
env: { MOCK_CLOSE_PROTOCOL_ON_INITIALIZE: '1' },
|
||||
disposeEofGraceMs: 50,
|
||||
disposeGraceMs: 50,
|
||||
spawn: spawnSubprocess,
|
||||
}).catch((cause: unknown) => cause)
|
||||
expect(error).toBeInstanceOf(Error)
|
||||
expect((error as Error).message).toBe(
|
||||
`subagent-acp: ${expectedFailure('stage: initialize; category: transport')}`,
|
||||
)
|
||||
})
|
||||
|
||||
it('reaps a child whose session/new response omits the session id', async () => {
|
||||
const tmp = mkdtempSync(join(tmpdir(), 'acp-malformed-session-'))
|
||||
const flushed = join(tmp, 'flushed')
|
||||
|
||||
Reference in New Issue
Block a user