Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/architecture/agent-scoped-extensions/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ global. Keep plugin availability global for DeepChat agents and unavailable for
2. Each DeepChat agent can independently configure its available normal MCP server set.
3. Switching DeepChat agents updates Skills and MCP enablement from that agent's policy.
4. Runtime prompt, skill tools, MCP definitions, and MCP calls use the session agent's policy.
DeepChat runtime passes resolved `enabledMcpServerIds` into tool catalog, fingerprint, and MCP
call/pre-check paths (plugin-owned MCP remains globally available when the plugin is enabled).
5. Existing global skill installation/import and MCP server definitions remain global resources.
6. Existing users retain compatible behavior: the built-in `deepchat` agent defaults to globally
available Skills and MCP servers; other DeepChat agents inherit its policy.
Expand Down
30 changes: 30 additions & 0 deletions docs/architecture/multi-agent-isolation/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Multi-Agent Isolation — Phase 0 Plan

## Approach

1. Treat host-agent allow-lists as runtime contracts, not settings-only metadata.
2. Reuse existing MCP `enabledServerIds` filters in `McpPresenter` / `ToolManager`.
3. Pass policy through the same seams already used for Skills (`AgentExtensionPolicy`).
4. Reset session-scoped security state when `agent_id` changes.

## Affected Interfaces

- `AgentExtensionPolicy` gains `enabledMcpServerIds`.
- `ToolDefinitionContext.enabledMcpServerIds` populated for DeepChat sessions.
- `ProcessControlCollaborators` exposes MCP allow-list + agent id for tool execution.
- `setSessionAgentContext` becomes a security boundary for transfer/rebind.

## Compatibility

- `null` / omitted allow-list: unrestricted normal MCP (current default after inheritance).
- `[]`: no normal MCP tools/calls; plugin-owned MCP still exempt.
- Plugin enablement remains global; `enabledPluginIds` stays omitted.
- No schema/IPC changes.

## Test Strategy

- Rewrite agentRuntimePresenter tool-discovery expectations to enforce MCP policy.
- Keep plugin policy omission assertion.
- Workspace allow-list unit coverage for call workdir only.
- Transfer/rebind clears command, file, settings, and MCP permissions and refilters skills.
- Dispatch rejects tool names missing from the current session catalog.
67 changes: 67 additions & 0 deletions docs/architecture/multi-agent-isolation/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Multi-Agent Isolation Contract

## User Need

DeepChat is a multi-host-agent application. Users configure independent DeepChat agents with
different Skills, MCP servers, tools, memory, and default permissions. Runtime behavior must match
those host-agent policies when sessions run concurrently or when history is transferred.

## Goal

Define the durable isolation matrix for host agents and the enforcement seams that keep Skills, MCP,
tools, memory, permissions, and workspace boundaries aligned with the session's host `agent_id`.

## Isolation Matrix

| Resource | Install / process | Policy owner | Runtime enforce |
| --- | --- | --- | --- |
| Skills catalog | global | agent `enabledSkillNames` | prompt / list / view / activate / catalog fingerprint |
| MCP servers | global process | agent `enabledMcpServerIds` | catalog + call + pre-check + fingerprint |
| Plugins | global | none (always global for DeepChat) | plugin lifecycle only |
| Plugin-owned MCP | global | exempt from agent MCP allow-list | always available when plugin enabled |
| Built-in agent tools | process | session `disabledAgentTools` | catalog filter + execution membership check |
| Memory | per-agent storage | agent memory settings | agentId-keyed rows / vectors |
| Permission approvals | session | session `permissionMode` + caches | conversation-scoped; all caches cleared on transfer |
| Workspace | session `project_dir` | session | tool allowed dirs from call workdir only |
| Tape / messages | session | session | session-scoped |
| ACP MCP | ACP process | ACP `agent_mcp_selections` | ACP session/call path |

## Acceptance Criteria

1. Two DeepChat agents with different `enabledMcpServerIds` expose different MCP tool catalogs at
runtime and cannot call disallowed servers.
2. Concurrent sessions with different workdirs do not widen each other's filesystem allow lists.
3. Transferring a session to another agent clears permission approvals, refilters active skills to
the target allow-list, clears plan/runtime skill activation, and invalidates tool caches.
4. Skills allow-list enforcement remains intact.
5. Plugins remain globally enabled for DeepChat agents; UI/docs distinguish global plugins from
agent-scoped Skills/MCP.
6. ACP agents continue using ACP MCP selections, not DeepChat `enabledMcpServerIds`.
7. Tool calls absent from the current session's tool definitions are rejected before dispatch, even
when a process-global tool mapper contains the same name.

## Constraints

- Do not copy skill files or MCP definitions per agent.
- Do not introduce per-agent plugin process isolation in this goal.
- Keep null/`undefined` allow-list semantics: inherit resolved builtin policy; `[]` disables the
category for normal (non-plugin) MCP servers.
- Preserve Presenter boundaries and existing route/event schemas.

## Non-goals

- Subagent target-agent full policy resolution (separate architecture slice).
- Process-level sandboxing between agents.
- Removing the DeepChat + `providerId=acp` compatibility path.
- Changing ACP Memory absence.

## Decisions

- DeepChat runtime must pass effective `enabledMcpServerIds` from
`resolveDeepChatAgentConfig(session.agentId)` into tool discovery and MCP call paths.
- Historical `enabledPluginIds` remains omitted from tool discovery forever.
- Tool workspace allow lists trust the conversation workdir argument, not a shared mutable manager
workspace field. A missing or failed conversation workdir lookup uses the isolated default
workspace and never falls back to the manager's last synchronized workspace.
- Transfer is a security boundary: command, file, settings, and MCP session approvals plus pinned
skills do not silently survive host change.
14 changes: 14 additions & 0 deletions docs/architecture/multi-agent-isolation/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Multi-Agent Isolation — Phase 0 Tasks

- [x] Write architecture isolation contract
- [x] Write issue specs for MCP allow-list, workspace leak, transfer reset
- [x] Wire `enabledMcpServerIds` into DeepChat extension policy / catalog / fingerprint / call
- [x] Fix AgentToolManager allowed-directories cross-session leak
- [x] Reset permissions / skills / plan on `setSessionAgentContext`
- [x] Block permission-retry success leak; align deferred tool options
- [x] Update tests and run format / i18n / lint / focused vitest
- [x] Phase 1: subagent target host policy isolation
- [x] Make missing conversation workdirs fall back to the isolated default, never manager state
- [x] Clear MCP session approvals through the aggregate permission port
- [x] Reject tool calls absent from the current session definitions
- [x] Add review regression tests and rerun validation
18 changes: 18 additions & 0 deletions docs/architecture/subagent-host-policy-isolation/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Subagent Host Policy Isolation — Plan

## Approach

1. Pass `parentAgentId` into subagent assignment resolution.
2. Expand `ResolvedSubagentAssignment` with `permissionMode`.
3. In `SessionAgentAssignmentPolicy.resolveSubagentAssignment`:
- self: inherit input surface
- cross-agent DeepChat: load target config for permission/tools/prompt/skill filter
4. Lifecycle initializes runtime with resolved `permissionMode`.
5. Lifecycle clones non-MCP approvals only when the resolved child agent matches `parentAgentId`.
6. Orchestrator supplies `parentAgentId` from the parent session.

## Tests

- Policy unit: self vs cross-agent vs ACP
- Lifecycle mock: uses resolved permissionMode
- Lifecycle permission inheritance: self-target clones; cross-agent starts clean
49 changes: 49 additions & 0 deletions docs/architecture/subagent-host-policy-isolation/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Subagent Host Policy Isolation

## User Need

When a parent DeepChat agent delegates work to a configured slot that targets another host agent,
the child session must not silently inherit the parent's full capability surface while writing
Memory and skills under the target agent identity.

## Goal

Make cross-agent subagent sessions resolve security-relevant policy from the **target host agent**,
while keeping intentional parent inheritance for workspace and model selection.

## Inheritance Matrix

| Field | `self` slot | Other target agent |
| --- | --- | --- |
| `agentId` / Memory | parent | target |
| `projectDir` | parent (same workdir rule) | parent (same workdir rule) |
| `providerId` / `modelId` | parent session | parent session |
| `permissionMode` | parent session | **target agent config** |
| `disabledAgentTools` | parent session | **target agent config** |
| `systemPrompt` | parent generation settings | **target agent config** |
| `activeSkills` | parent session pins | parent pins **∩ target `enabledSkillNames`** |
| Permission approvals | inherit non-MCP session approvals | **start clean** |
| MCP / plugins | runtime host policy (agentId) | runtime host policy (target agentId) |

## Acceptance Criteria

1. Self-target subagent sessions keep parent permissionMode, disabled tools, skills, and generation
settings.
2. Cross-agent subagent sessions apply target permissionMode, disabled tools, and system prompt.
3. Cross-agent active skills are filtered by the target agent allow-list (`[]` means none).
4. Workdir remains the parent session workdir for both cases.
5. ACP subagent targets remain ACP-shaped (no DeepChat tool/skill inheritance).
6. Only self-target children inherit parent session approvals. Cross-agent children start with
empty approval caches, and MCP temporary approvals are never cloned.

## Constraints

- No renderer/IPC schema change required beyond existing createSubagentSession fields.
- Do not enable nested subagents.
- Do not change run timeout / concurrency guardrails.

## Non-goals

- Bubbling child permission UI into the parent interaction queue (later UX slice).
- Per-child model overrides from target default model.
- Process-level isolation of MCP/plugin runtimes.
9 changes: 9 additions & 0 deletions docs/architecture/subagent-host-policy-isolation/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Subagent Host Policy Isolation — Tasks

- [x] Spec / plan
- [x] Ports + policy resolution
- [x] Lifecycle + orchestrator wiring
- [x] Tests
- [x] format / i18n / lint
- [x] Restrict permission inheritance to self-target children
- [x] Add self-target and cross-agent approval regression coverage
16 changes: 16 additions & 0 deletions docs/issues/acp-tool-progress-permission-ui/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# ACP Tool Progress Pseudo Permission UI

## Issue

Ordinary ACP `tool_call` / status progress was projected as `action_type: 'tool_call_permission'`,
which mixed narrative tool progress with real permission interactions.

## Fix

Emit reasoning events for tool progress only; reserve permission action blocks for protocol
permission requests.

## Tasks

- [x] Remove pseudo permission action blocks from `acpContentMapper`
- [x] Regression test
40 changes: 40 additions & 0 deletions docs/issues/agent-mcp-allowlist-runtime-enforce/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Agent MCP Allow-List Runtime Enforce

## Issue

DeepChat agent config and Plugins UI store per-agent `enabledMcpServerIds`, but session tool
discovery does not pass that allow-list into `ToolPresenter` / MCP catalog or call paths. Agents with
`enabledMcpServerIds: []` can still list and invoke globally running MCP servers.

## Impact

- Host-agent MCP isolation is a product lie: settings appear agent-scoped, runtime is global.
- Violates `agent-scoped-extensions` AC4.
- Concurrent multi-agent sessions share the full MCP surface.

## Root Cause

`resolveAgentExtensionPolicy` only returns `enabledSkillNames`.
`createSessionToolCatalogPort` omits `enabledMcpServerIds` from the tool definition context.
A regression test (`omits historical MCP and plugin policies`) incorrectly locked MCP omission
together with historical plugin policy removal.

## Fix Plan

- Extend extension policy with resolved `enabledMcpServerIds`.
- Pass allow-list into catalog context, fingerprint, execute options, and deferred execution.
- Rely on existing MCP `enabledServerIds` filtering for definitions and calls.
- Keep plugin-owned MCP exemption and omit `enabledPluginIds`.
- Replace the omit-MCP expectation with enforce expectations.

## Tasks

- [x] Runtime policy + catalog + fingerprint + dispatch/deferred wiring
- [x] Rewrite presenter tests for enforce semantics
- [x] format / i18n / lint / focused tests

## Validation

- Agent with `enabledMcpServerIds: []` yields no normal MCP tools in discovery context.
- Agent with a non-empty list passes that list into discovery.
- `enabledPluginIds` remains absent from discovery context.
37 changes: 37 additions & 0 deletions docs/issues/agent-tool-workspace-cross-session-leak/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Agent Tool Workspace Cross-Session Leak

## Issue

`AgentToolManager` is a process singleton. `buildAllowedDirectories` adds both the call's workspace
path and the manager's last `syncContext` workspace path. Concurrent sessions with different
project directories can therefore allow filesystem access into another session's workdir.

## Impact

Multi-agent / multi-session isolation fails for local file tools even when each session has a
distinct `project_dir`.

## Root Cause

Shared mutable `this.agentWorkspacePath` is used as a fallback when a conversation workdir is null
or cannot be resolved, so the last synchronized session workspace can still enter another
conversation's allow list.

## Fix Plan

- Build allow lists from the call's workspace path only (plus skill roots, runtime roots, approvals).
- Do not add the manager's last synced workspace path.
- When a conversation is present but has no resolved workdir, use the isolated default workspace;
reserve manager state only for calls that have no conversation context.

## Tasks

- [x] Remove shared workspace merge from `buildAllowedDirectories`
- [x] Covered by toolPresenter suite + multi-agent isolation contract
- [x] format / lint / focused tests
- [x] Cover a null/failed lookup after another session synchronized a different workspace

## Validation

- Allowed directories for workdir `/a` do not include a previously synced `/b`.
- Session `/a` with no resolved project directory still cannot inherit `/b` from manager state.
39 changes: 39 additions & 0 deletions docs/issues/agent-transfer-permission-skill-reset/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Agent Transfer Permission / Skill Reset

## Issue

Moving a session to another DeepChat agent updates `agent_id`, model, permission mode, and disabled
tools, but can keep conversation-scoped command/file/settings/MCP approvals, unfiltered active skill
pins, plan state, and runtime-activated skills from the previous host.

## Impact

A session transferred from a permissive agent to a strict agent can retain prior approvals and skill
pins that the target host policy would not grant.

## Root Cause

`setSessionAgentContext` rebinds identity and caches but does not clear session security state.

## Fix Plan

On agent rebind:

- clear session permission approvals
- include MCP `ToolManager.sessionPermissions` in the aggregate clear operation
- clear agent plan state
- clear runtime-activated skills
- refilter persisted active skills to the target `enabledSkillNames`
- keep existing tool-profile / system-prompt invalidation

## Tasks

- [x] Implement reset in `setSessionAgentContext`
- [x] Extend skillPresenter port with `setActiveSkills` where needed
- [x] format / lint / focused tests
- [x] Clear MCP temporary approvals and define them as non-cloneable

## Validation

- After transfer, permission caches for the session are empty.
- Active skills are a subset of the target agent's allow-list (or empty when none allowed).
39 changes: 39 additions & 0 deletions docs/issues/chat-generation-lifecycle-timeouts/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Chat / Session Lifecycle Timeouts and Locks

## Issue

ChatService used a fake stream lock and agentType preflight that did not match enqueue-first
generation ownership. Session create used a 5s timeout that could fail ACP cold start. Session
delete aborted row removal when backend cleanup failed, leaving zombie sessions.

## Impact

Concurrent accepts could survive stop, timeout cleanup could hang or replace the original error,
and a late session create could publish after the route already reported failure.

## Root Cause

- ChatService stored one accept controller per session and did not bound all cleanup paths.
- `Scheduler.timeout` races but does not cancel the mutating `createSession` task.

## Fix

- ChatService: retain every concurrent accept controller per session, bound best-effort cleanup, and
convert stop cleanup timeouts into an honest `{ stopped: false }` result
- SessionService: do not race the mutating create operation against a non-cancelling route timeout;
keep the longer list timeout for read availability
- SessionDeletionTransaction: best-effort stages, always attempt row delete

## Tasks

- [x] Retain and abort every concurrent accept controller per session
- [x] Bound best-effort cancel and convert stop cleanup timeout into `{ stopped: false }`
- [x] Preserve the original send timeout when cleanup fails synchronously or times out
- [x] Remove the non-cancelling route timeout around session creation
- [x] Update timeout assertions and add lifecycle regression tests

## Validation

- Concurrent send accepts are both aborted by one stop request.
- Stop cleanup timeout returns `{ stopped: false }`; send timeout remains the caller-visible error.
- Session create is invoked once without a scheduler race, while session list keeps its 15s timeout.
5 changes: 2 additions & 3 deletions src/main/agent/acp/runtime/acpContentMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,15 +235,14 @@ export class AcpContentMapper {

this.emitToolCallStartIfNeeded(state, payload)

// Tool progress is narrative only. Never emit tool_call_permission action blocks here —
// those are reserved for real session/request_permission interactions.
const shouldEmitReasoning =
update.sessionUpdate === 'tool_call' || (status && status !== previousStatus)
if (shouldEmitReasoning) {
const reasoningText = this.buildToolCallReasoning(state.toolName, status)
if (reasoningText) {
payload.events.push(createStreamEvent.reasoning(reasoningText))
payload.blocks.push(
this.createBlock('action', reasoningText, { action_type: 'tool_call_permission' })
)
}
}

Expand Down
Loading
Loading