diff --git a/apps/cli/config/agent-presets/standard/agent.cordis.yml b/apps/cli/config/agent-presets/standard/agent.cordis.yml index 41d1d81a90..8eaa78d940 100644 --- a/apps/cli/config/agent-presets/standard/agent.cordis.yml +++ b/apps/cli/config/agent-presets/standard/agent.cordis.yml @@ -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 ─────────────────────────────────────────────────────────────── diff --git a/packages/bundle/web-app/cordis.patch.yml b/packages/bundle/web-app/cordis.patch.yml index 3ccc31dc95..fb2b660805 100644 --- a/packages/bundle/web-app/cordis.patch.yml +++ b/packages/bundle/web-app/cordis.patch.yml @@ -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 diff --git a/packages/host/apiproxy/src/api-proxy.ts b/packages/host/apiproxy/src/api-proxy.ts index 88d91a0328..a70977155a 100644 --- a/packages/host/apiproxy/src/api-proxy.ts +++ b/packages/host/apiproxy/src/api-proxy.ts @@ -1179,30 +1179,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 { - 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, @@ -1823,7 +1799,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, diff --git a/packages/host/apiproxy/tests/api-proxy-agent-preset.spec.ts b/packages/host/apiproxy/tests/api-proxy-agent-preset.spec.ts index 8acc1b9616..ad951a6520 100644 --- a/packages/host/apiproxy/tests/api-proxy-agent-preset.spec.ts +++ b/packages/host/apiproxy/tests/api-proxy-agent-preset.spec.ts @@ -363,37 +363,3 @@ describe('agentPreset.select', () => { 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) - }) -})