fix(session-title): preserve agent turn outcomes
This commit is contained in:
@@ -20,7 +20,7 @@ The plugin answers `shutdown`, disposes SDK-owned agents and subscriptions to qu
|
||||
|
||||
## Wire notes
|
||||
|
||||
`initialize.serverInfo.name` is the wire-stable `deepseek-harness-sdk-runtime`. A session accepts one in-flight prompt; overlap fails immediately, other sessions remain independent, and the session is reusable after settlement. Persistence roots and persona come from `cordis.yml`.
|
||||
`initialize.serverInfo.name` is the wire-stable `deepseek-harness-sdk-runtime`. A session accepts one in-flight prompt; overlap fails immediately, other sessions remain independent, and the session is reusable after settlement. `session.finished` reports that prompt's message-triggered turn outcome; later injection or plugin-owned zero-step turns still stream as `session.event` notifications but cannot replace the prompt status. Persistence roots and persona come from `cordis.yml`.
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { resolve } from 'node:path'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import type { Agent, AgentHandle } from '@deepseek-ai/dsh-agent'
|
||||
import { carrierKeyOf, type Scoped } from '@deepseek-ai/dsh-scope'
|
||||
import { SessionId, type TurnEndReason } from '@deepseek-ai/dsh-session'
|
||||
import { findLastMessageTurnEnd, SessionId, type TurnEndReason } from '@deepseek-ai/dsh-session'
|
||||
import type SubagentService from '@deepseek-ai/dsh-subagent'
|
||||
import type { SubagentRunEndInfo } from '@deepseek-ai/dsh-subagent'
|
||||
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
||||
@@ -93,7 +93,9 @@ export class HarnessSdkServer {
|
||||
this.disposers.push(ctx.on('session/event', (session, event) => {
|
||||
if (event.type === 'turn/end') {
|
||||
const rec = this.sessions.get(String(session.id))
|
||||
if (rec) rec.lastTurnEnd = event.data.reason
|
||||
if (rec && findLastMessageTurnEnd(session.events)?.seq === event.seq) {
|
||||
rec.lastTurnEnd = event.data.reason
|
||||
}
|
||||
}
|
||||
this.transport.notify('session.event', { sessionId: String(session.id), event })
|
||||
}))
|
||||
|
||||
@@ -7,7 +7,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { type Agent, type AgentHandle } from '@deepseek-ai/dsh-agent'
|
||||
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo'
|
||||
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
||||
@@ -215,6 +215,64 @@ describe('HarnessSdkServer', () => {
|
||||
expect(otherHandle.dispose).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('reports the message-turn outcome when a later non-message turn settles before idle', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
const transport = new FakeTransport()
|
||||
const server = new HarnessSdkServer(ctx, transport) as unknown as {
|
||||
prompt(params: { sessionId: string; contentBlocks: { type: 'text'; text: string }[] }): Promise<unknown>
|
||||
sessions: Map<string, { handle: AgentHandle; lastTurnEnd: undefined; activePrompt: boolean }>
|
||||
shutdown(): Promise<Record<string, never>>
|
||||
}
|
||||
const session = ctx.sessions.create(SessionId('message-outcome'))
|
||||
const agent = {
|
||||
session,
|
||||
send(content: { type: 'text'; text: string }[]) {
|
||||
session.append('turn/start', {
|
||||
turn: 1,
|
||||
trigger: { kind: 'message', source: { kind: 'user' } },
|
||||
})
|
||||
session.append('user/message', {
|
||||
content,
|
||||
source: { kind: 'user' },
|
||||
}, { surfaceOp: 'append' })
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'max-tokens' } })
|
||||
session.append('turn/start', {
|
||||
turn: 2,
|
||||
trigger: { kind: 'injection', source: { kind: 'plugin', plugin: 'late-metadata' } },
|
||||
})
|
||||
session.append('context/message', {
|
||||
content: [{ type: 'text', text: 'late metadata' }],
|
||||
source: { kind: 'plugin', plugin: 'late-metadata' },
|
||||
}, { surfaceOp: 'append' })
|
||||
session.append('turn/end', { turn: 2, reason: { kind: 'completed' } })
|
||||
},
|
||||
whenIdle: () => Promise.resolve(),
|
||||
} as unknown as Agent
|
||||
server.sessions.set('message-outcome', {
|
||||
handle: { agent, dispose: () => Promise.resolve() },
|
||||
lastTurnEnd: undefined,
|
||||
activePrompt: false,
|
||||
})
|
||||
|
||||
await server.prompt({
|
||||
sessionId: 'message-outcome',
|
||||
contentBlocks: [{ type: 'text', text: 'bounded prompt' }],
|
||||
})
|
||||
|
||||
expect(transport.notifications.findLast(notification => notification.method === 'session.finished'))
|
||||
.toEqual({
|
||||
method: 'session.finished',
|
||||
params: {
|
||||
sessionId: 'message-outcome',
|
||||
status: 'error',
|
||||
reason: { kind: 'max-tokens' },
|
||||
},
|
||||
})
|
||||
await server.shutdown()
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('notifies the host when a child session is created with parent lineage', async () => {
|
||||
const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-subagent-'))
|
||||
const ctx = await makeHarness(storageDir)
|
||||
|
||||
Reference in New Issue
Block a user