mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge branch 'stack/agent-profiles-5-web-ui' into stack/agent-profiles-8-authoring
# Conflicts: # packages/host/apiproxy/tests/api-proxy-agent-preset.spec.ts
This commit is contained in:
@@ -91,23 +91,14 @@
|
||||
|
||||
# ── goals ───────────────────────────────────────────────────────────────────
|
||||
|
||||
- id: goals
|
||||
name: cordis:group
|
||||
group: true
|
||||
isolate:
|
||||
goals: true
|
||||
config:
|
||||
- id: goal
|
||||
name: '@deepseek-ai/dsh-goal'
|
||||
|
||||
- id: goal-session
|
||||
name: '@deepseek-ai/dsh-goal-session'
|
||||
|
||||
- id: command-goal
|
||||
name: '@deepseek-ai/dsh-command-goal'
|
||||
|
||||
- id: tool-goal
|
||||
name: '@deepseek-ai/dsh-tool-goal'
|
||||
# Only the model-facing tool. The goal SERVICE, its session driver, and the
|
||||
# `/goal` command stay on the host plane: the Gateway serves the goal domain as
|
||||
# Remote endpoints whose receiver comes from a generated descriptor, so it
|
||||
# resolves `goals` on the host and an entry-local realm here would hide it. The
|
||||
# registry is keyed by session anyway, so one host instance serves every
|
||||
# session. What a preset chooses is whether its agent can call the goal tool.
|
||||
- id: tool-goal
|
||||
name: '@deepseek-ai/dsh-tool-goal'
|
||||
|
||||
# ── plan mode ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -242,14 +242,14 @@
|
||||
- id: tool-skill
|
||||
disabled: true
|
||||
|
||||
- id: goal
|
||||
disabled: true
|
||||
|
||||
- id: goal-session
|
||||
disabled: true
|
||||
|
||||
- id: command-goal
|
||||
disabled: true
|
||||
# The goal SERVICE, its session driver, and the `/goal` command STAY on the
|
||||
# host plane; only the model-facing tool moves. The Gateway serves the goal
|
||||
# domain as Remote endpoints, and a Remote method picks its receiver Service
|
||||
# from a generated descriptor — it resolves `goals` on the host, so a
|
||||
# per-session realm would answer `service-unavailable` for every browser call.
|
||||
# That is the `bash-env` criterion read from the other side: injection is not
|
||||
# the only host relationship a Service can have. The registry is keyed by
|
||||
# session, so one host instance serves every session exactly as before presets.
|
||||
|
||||
- id: tool-goal
|
||||
disabled: true
|
||||
|
||||
@@ -1211,30 +1211,6 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* The agent whose layer holds this session's tool presenters.
|
||||
*
|
||||
* A preset registers its tools into the agent's OWN layer, so a transcript
|
||||
* read while no agent exists finds no presenter at all and every card
|
||||
* degrades to the generic renderer — silently, because a viewless entry is
|
||||
* also what a tool with no presenter produces. Reading therefore resolves
|
||||
* the agent, through the same deduplicated resume every other session method
|
||||
* takes, whenever a roster is composed.
|
||||
*
|
||||
* A deployment composing no roster keeps the storage-only read it always
|
||||
* had: its tools are in the host layer, which needs no agent to address. A
|
||||
* resolution failure is not a read failure either — the transcript still
|
||||
* serves, with the generic cards it would have rendered anyway.
|
||||
* @param sessionId - the transcript being read.
|
||||
* @returns the agent to resolve presenters against, or undefined for none.
|
||||
*/
|
||||
async function presenterAgentFor(sessionId: SessionId): Promise<Agent | undefined> {
|
||||
const live = ctx.get('agents')?.get(sessionId)
|
||||
if (live !== undefined || ctx.get('agentPresets') === undefined) return live
|
||||
const found = await agentFor(sessionId)
|
||||
return 'error' in found ? undefined : found.agent
|
||||
}
|
||||
|
||||
/** Read one transcript cut and optional projection baseline without acquiring an Agent owner. */
|
||||
async function historyStateFor(
|
||||
sessionId: SessionId,
|
||||
@@ -1855,7 +1831,11 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
details: {},
|
||||
})
|
||||
}
|
||||
const page = historyPage(ctx, state.events, beforeSeq, maxMessages, await presenterAgentFor(sessionId))
|
||||
// `ctx.get`, not `ctx.agents`: this is the COLD path, and a caller may
|
||||
// serve history from storage with no agent registry composed at all.
|
||||
// An absent registry means no live agent, which is the same answer a
|
||||
// present one gives here — presenters fall back to the global layer.
|
||||
const page = historyPage(ctx, state.events, beforeSeq, maxMessages, ctx.get('agents')?.get(sessionId))
|
||||
return ok(request, {
|
||||
events: page.events,
|
||||
hasMore: page.hasMore,
|
||||
|
||||
@@ -438,37 +438,3 @@ describe('authoring over the wire', () => {
|
||||
expect(response.result.error.code).toBe('agent-preset-not-found')
|
||||
})
|
||||
})
|
||||
|
||||
describe('session.history presenters', () => {
|
||||
it('resolves the live agent so a preset-composed presenter is reachable', async () => {
|
||||
const { api, ctx } = await harness(['standard'])
|
||||
await api.sessions.create(request({ sessionId: SessionId('h1'), agentPreset: 'standard' }))
|
||||
|
||||
const response = await api.sessions.history(request({ sessionId: SessionId('h1') }))
|
||||
|
||||
expect(response.result.ok).toBe(true)
|
||||
expect(ctx.agents.get(SessionId('h1'))).toBeDefined()
|
||||
})
|
||||
|
||||
it('serves the transcript when no agent can be resolved for it', async () => {
|
||||
// The roster is composed, so the read tries; this harness resumes nothing.
|
||||
// A resolution failure is not a read failure — the transcript still
|
||||
// serves, with the generic cards a viewless entry renders.
|
||||
const { api } = await harness(['standard'])
|
||||
|
||||
const response = await api.sessions.history(request({ sessionId: SessionId('h2') }))
|
||||
|
||||
expect(response.result.ok).toBe(false)
|
||||
const failure = response.result as { ok: false; error: { code: string } }
|
||||
expect(failure.error.code).toBe('session-not-found')
|
||||
})
|
||||
|
||||
it('keeps the storage-only read when the deployment composes no roster', async () => {
|
||||
const { api } = await harness()
|
||||
await api.sessions.create(request({ sessionId: SessionId('h3') }))
|
||||
|
||||
const response = await api.sessions.history(request({ sessionId: SessionId('h3') }))
|
||||
|
||||
expect(response.result.ok).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user