Browse Source

Merge branch 'stack/agent-profiles-3-wire' into stack/agent-profiles-5-web-ui

# Conflicts:
#	packages/host/apiproxy/tests/api-proxy-agent-preset.spec.ts
Yichen Jiang 2 tháng trước cách đây
mục cha
commit
5e892aa6dc

+ 8 - 17
apps/cli/config/agent-presets/standard/agent.cordis.yml

@@ -91,23 +91,14 @@
 
 
 # ── goals ───────────────────────────────────────────────────────────────────
 # ── 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 ───────────────────────────────────────────────────────────────
 # ── plan mode ───────────────────────────────────────────────────────────────
 
 

+ 8 - 8
packages/bundle/web-app/cordis.patch.yml

@@ -242,14 +242,14 @@
 - id: tool-skill
 - id: tool-skill
   disabled: true
   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
 - id: tool-goal
   disabled: true
   disabled: true

+ 5 - 25
packages/host/apiproxy/src/api-proxy.ts

@@ -1179,30 +1179,6 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
     return undefined
     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. */
   /** Read one transcript cut and optional projection baseline without acquiring an Agent owner. */
   async function historyStateFor(
   async function historyStateFor(
     sessionId: SessionId,
     sessionId: SessionId,
@@ -1823,7 +1799,11 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
             details: {},
             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, {
         return ok(request, {
           events: page.events,
           events: page.events,
           hasMore: page.hasMore,
           hasMore: page.hasMore,

+ 0 - 34
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')
     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)
-  })
-})