fix(web-app,agent-presets): keep the goal service on the host plane
The Gateway serves the goal domain as Remote endpoints, and a Remote method picks its receiver Service from a generated descriptor — `clear(agent, ref)` takes its agent as a PARAMETER, so the invocation is direct and the receiver resolves on the host. Behind the preset's entry-local realm there was nothing to resolve, and every browser goal call answered `service-unavailable`: the composer's Clear goal button left the bar on screen. That is the `bash-env` criterion read from the other side. Injection is not the only host relationship a Service can have; being READ from a host row is one too. The registry is keyed by session, so one host instance serves every session exactly as it did before presets. The preset keeps the model-facing tool, which is the choice a preset is for. Also reverts the cold-transcript presenter resolve: resuming an agent to reach its presenters made the context meter drop its cache and token counts on every cold-opened session, trading one silent degradation for another. The card degradation it addressed is diagnosed and still open.
This commit is contained in:
@@ -1127,30 +1127,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,
|
||||
@@ -1780,7 +1756,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,
|
||||
|
||||
Reference in New Issue
Block a user