diff --git a/.agents/skills/ai-elements/SKILL.md b/.agents/skills/ai-elements/SKILL.md
index 6c6ed85d..d806db10 100644
--- a/.agents/skills/ai-elements/SKILL.md
+++ b/.agents/skills/ai-elements/SKILL.md
@@ -152,4 +152,4 @@ If none of these answers help, open an [issue on GitHub](https://github.com/verc
## Available Components
-See the `references/` folder for detailed documentation on each component. Paths beginning with `scripts/` in those references are examples relative to this skill directory, not repository-root scripts. They are component examples, not installed application features.
+See the `references/` folder for detailed documentation on each component.
diff --git a/.agents/skills/ai-elements/references/conversation.md b/.agents/skills/ai-elements/references/conversation.md
index b885928e..96ebb120 100644
--- a/.agents/skills/ai-elements/references/conversation.md
+++ b/.agents/skills/ai-elements/references/conversation.md
@@ -21,7 +21,7 @@ npx ai-elements@latest add conversation
## Usage with AI SDK
-Build a simple conversational UI with `Conversation` and [`PromptInput`](prompt-input.md):
+Build a simple conversational UI with `Conversation` and [`PromptInput`](/components/prompt-input):
Add the following component to your frontend:
diff --git a/.agents/skills/ai-elements/references/image.md b/.agents/skills/ai-elements/references/image.md
index 35ccfa22..b1677608 100644
--- a/.agents/skills/ai-elements/references/image.md
+++ b/.agents/skills/ai-elements/references/image.md
@@ -9,7 +9,7 @@ See THIRD-PARTY-NOTICES.md in the repository root for license and provenance.
Displays AI-generated images from the AI SDK.
-The `Image` component displays AI-generated images from the AI SDK. It accepts a `Experimental_GeneratedImage` object from the AI SDK's `generateImage` function and automatically renders it as an image.
+The `Image` component displays AI-generated images from the AI SDK. It accepts a [`Experimental_GeneratedImage`](/docs/reference/ai-sdk-core/generate-image) object from the AI SDK's `generateImage` function and automatically renders it as an image.
See `scripts/image.tsx` for this example.
diff --git a/.agents/skills/ai-elements/references/inline-citation.md b/.agents/skills/ai-elements/references/inline-citation.md
index 5a34ad32..170d736a 100644
--- a/.agents/skills/ai-elements/references/inline-citation.md
+++ b/.agents/skills/ai-elements/references/inline-citation.md
@@ -21,7 +21,7 @@ npx ai-elements@latest add inline-citation
## Usage with AI SDK
-Build citations for AI-generated content using `experimental_generateObject`.
+Build citations for AI-generated content using [`experimental_generateObject`](/docs/reference/ai-sdk-ui/use-object).
Add the following component to your frontend:
diff --git a/.agents/skills/ai-elements/references/prompt-input.md b/.agents/skills/ai-elements/references/prompt-input.md
index b3606ff7..a35686b2 100644
--- a/.agents/skills/ai-elements/references/prompt-input.md
+++ b/.agents/skills/ai-elements/references/prompt-input.md
@@ -21,7 +21,7 @@ npx ai-elements@latest add prompt-input
## Usage with AI SDK
-Build a fully functional chat app using `PromptInput`, [`Conversation`](conversation.md) with a model picker:
+Build a fully functional chat app using `PromptInput`, [`Conversation`](/components/conversation) with a model picker:
Add the following component to your frontend:
@@ -368,7 +368,7 @@ See `scripts/prompt-input-tooltip.tsx` for this example.
### Attachments
-Attachment components have been moved to a separate module. See the [Attachment](attachments.md) component documentation for details on ``, ``, ``, ``, and ``.
+Attachment components have been moved to a separate module. See the [Attachment](/components/attachment) component documentation for details on ``, ``, ``, ``, and ``.
### ``
diff --git a/.agents/skills/ai-elements/references/reasoning.md b/.agents/skills/ai-elements/references/reasoning.md
index 13808e35..f7e0cf2b 100644
--- a/.agents/skills/ai-elements/references/reasoning.md
+++ b/.agents/skills/ai-elements/references/reasoning.md
@@ -164,7 +164,7 @@ export async function POST(req: Request) {
Use the `Reasoning` component when your model outputs thinking content as a single block or continuous stream (Deepseek R1, Claude with extended thinking, etc.).
-If your model outputs discrete, labeled steps (search queries, tool calls, distinct thought stages), consider using the [Chain of Thought](chain-of-thought.md) component instead for a more structured visual representation.
+If your model outputs discrete, labeled steps (search queries, tool calls, distinct thought stages), consider using the [Chain of Thought](/components/chain-of-thought) component instead for a more structured visual representation.
## Features
diff --git a/.agents/skills/ai-elements/references/task.md b/.agents/skills/ai-elements/references/task.md
index b117d283..961e405f 100644
--- a/.agents/skills/ai-elements/references/task.md
+++ b/.agents/skills/ai-elements/references/task.md
@@ -21,7 +21,7 @@ npx ai-elements@latest add task
## Usage with AI SDK
-Build a mock async programming agent using `experimental_generateObject`.
+Build a mock async programming agent using [`experimental_generateObject`](/docs/reference/ai-sdk-ui/use-object).
Add the following component to your frontend:
diff --git a/.agents/skills/ai-elements/references/tool.md b/.agents/skills/ai-elements/references/tool.md
index 1d2894d2..87a57fd0 100644
--- a/.agents/skills/ai-elements/references/tool.md
+++ b/.agents/skills/ai-elements/references/tool.md
@@ -21,7 +21,7 @@ npx ai-elements@latest add tool
## Usage in AI SDK
-Build a simple stateful weather app that renders the last message in a tool using `useChat`.
+Build a simple stateful weather app that renders the last message in a tool using [`useChat`](/docs/reference/ai-sdk-ui/use-chat).
Add the following component to your frontend:
diff --git a/.agents/skills/architecture-governance/SKILL.md b/.agents/skills/architecture-governance/SKILL.md
index 4882d980..9e3ca2b6 100644
--- a/.agents/skills/architecture-governance/SKILL.md
+++ b/.agents/skills/architecture-governance/SKILL.md
@@ -1,6 +1,6 @@
---
name: architecture-governance
-description: Apply the repository's architecture policy to code changes by generating a bounded context package, checking module and layer boundaries, and reporting baseline-aware violations. Use for any code change; skip for documentation-only work.
+description: Check CCbuddy module and layer boundaries for code changes. Generate bounded context for cross-module or ownership changes; skip documentation-only work.
---
# Architecture governance
@@ -9,16 +9,16 @@ Use this skill before editing code in the CCbuddy repository. It is a design gui
## Before writing code
-1. Identify changed files and their modules with `pnpm architecture:check --changed`.
-2. Run `pnpm architecture:context ` (or the reusable wrapper `node .agents/skills/architecture-governance/scripts/context-package.mjs `). Read the target contract, directly referenced contracts, and any existing relevant spec and tests before opening broad implementation files. Do not assume a documentation or test path exists; verify it in the checkout.
-3. Write or update the spec before implementation; create its directory when needed. State the behavior, ownership, invariants, failure semantics, and migration boundary in the spec.
-4. Make a short design decision before coding:
+1. Identify intended files and modules from the task, contracts, and existing tests. Run `pnpm architecture:check --changed` for the current baseline; a clean diff does not identify files you have not edited yet.
+2. For cross-module changes, state ownership changes, or an unclear contract, run `pnpm architecture:context ` (or `node .agents/skills/architecture-governance/scripts/context-package.mjs `). For a local change with a known contract, read only the relevant contract, spec, and affected tests; expand when a concrete dependency or evidence gap requires it.
+3. Apply the spec and regression-evidence rules in [AGENTS.md](../../../AGENTS.md). New behavior needs a spec before implementation; restoring already specified behavior needs regression evidence, not a duplicate feature document. Pure refactors update docs only when contracts change.
+4. For changes to mutable state, asynchronous behavior, or module boundaries, make the relevant design decisions before coding:
- **One owner:** name the single component that owns each piece of mutable state. Other layers read through its contract and send commands; they do not keep a second accepted queue, cache, or derived truth.
- **One path:** reuse an existing command, service, hook, adapter, or contract when it already expresses the behavior. Do not create a parallel helper for the same responsibility.
- - **Explicit boundaries:** choose the layer for every new file and the public contract for every cross-module edge. Use the module's declared `layers` and `layerOrder`; a file may import only its own layer or lower ones through their public surface. `domain` is pure (no IO, no `await` on the world), `app` decides side effects through ports, `adapters` executes them, `ui` depends only on this module's `contract.ts`. Quick test: needs `await`? not domain. Knows it is sqlite / MessagePort / a timer? adapters.
- - **Explicit time:** for asynchronous or remote behavior, write the event order, owner/lease, idempotency key, stale-result rule, replay/resume boundary, and desktop versus mobile delivery kind before implementation.
- - **Bounded context:** prefer the generated reading package over copying whole implementations into the prompt. Read more only when a contract or test proves it is necessary.
-5. If the change crosses modules or changes state ownership, include the decision in the spec and add or update the module contract before implementation.
+ - **Explicit boundaries:** choose the layer for every new file and the public contract for every cross-module edge. Keep `ui → hooks/services → app → domain → adapters` and keep IO out of domain.
+ - **Explicit time:** for asynchronous or remote behavior, write the event order, owner/lease, idempotency key, stale-result rule, replay/resume boundary, and desktop versus Web/SSH delivery kind before implementation.
+ - **Bounded context:** use the relevant contracts, specs, and tests. A generated reading package helps when dependencies are unclear; do not require a whole-module reading pass for every local edit.
+5. If the change alters a public contract, ownership, or allowed dependency, record that decision in the relevant spec/contract before implementation. Editing files in two modules alone does not require a new document. Use the root task boundary for when to consult the user; ordinary authorized implementation choices proceed autonomously.
Use this compact design sketch while planning stateful changes:
@@ -32,19 +32,17 @@ For remote or streaming changes, make the delivery boundary explicit:
```text
desktop: continuous ── direct live stream ──┐
├─ same owner and sequence
-mobile: replayable ─ snapshot + gap repair ┘
+Web/SSH: replayable ─ snapshot + gap repair ┘
```
## During and after editing
6. Keep changes inside the declared module and its allowed layer direction. Add a module dependency or public contract before introducing a cross-module edge.
-7. Run `pnpm architecture:check --changed` again after editing. Report new violations separately from baseline violations, along with changed modules, tests, state owners, event-order assumptions, and net line changes.
+7. Run `pnpm architecture:check --changed` after editing. Report new violations separately from baseline violations; include owners and event-order assumptions when affected. Follow the root validation matrix, finish after required checks pass, and expand checks only for new edits, failures, or unresolved risks.
-The executable policy is `architecture-policy.yaml`; do not duplicate its rules in this file or in AGENTS.md. Use `pnpm architecture:baseline:update` only when a reviewed change intentionally changes the accepted legacy baseline. CI never refreshes baseline automatically.
+The executable policy is `architecture-policy.yaml`; keep its detailed rule definitions there. Check the actual managed-module coverage before claiming the gate verifies a boundary: a passing result does not prove unconfigured modules or runtime semantics are correct. Use `pnpm architecture:baseline:update` only when a reviewed change intentionally changes the accepted legacy baseline. CI never refreshes baseline automatically.
-When adding source, identify its owning module. If a new managed module is required, register its roots, dependencies, layers and public entrypoints in `architecture-policy.yaml`, and keep the local manifest consistent with that policy. Use the existing managed modules and the fixture below as examples.
-
-For a new managed module, provide `module.ts`, `contract.ts`, `contract.example.ts`, and a short `CONTRACT.md`. Keep runtime and persistence details behind the contract. Prefer typed service calls for one-to-one interactions, commands for state changes, and typed events for broadcast facts.
+For a new managed module, provide `module.ts`, `contract.ts`, `contract.example.ts`, `contract.test.ts`, and a short `CONTRACT.md`. Keep runtime and persistence details behind the contract. Prefer typed service calls for one-to-one interactions, commands for state changes, and typed events for broadcast facts.
See [policy-schema.md](references/policy-schema.md), [module-contract.md](references/module-contract.md), and [rule-catalog.md](references/rule-catalog.md) when the change needs their detailed guidance. The [golden-module](references/golden-module) fixture is the smallest compliant example.
See [ai-guidance.md](references/ai-guidance.md) for the anti-patterns this workflow is designed to prevent and the questions an agent must answer before proposing code.
diff --git a/.agents/skills/architecture-governance/agents/openai.yaml b/.agents/skills/architecture-governance/agents/openai.yaml
index b81ba6db..7411ef53 100644
--- a/.agents/skills/architecture-governance/agents/openai.yaml
+++ b/.agents/skills/architecture-governance/agents/openai.yaml
@@ -1,4 +1,4 @@
interface:
display_name: Architecture Governance
short_description: Check module boundaries and prepare a bounded code context
- default_prompt: Use $architecture-governance before editing code to inspect the affected module, generate its context package, and run the architecture checks before and after the change.
+ default_prompt: Use $architecture-governance to check affected module boundaries. Generate context for cross-module, ownership, or unclear-contract changes, and run the architecture checks before and after code changes.
diff --git a/.agents/skills/architecture-governance/references/ai-guidance.md b/.agents/skills/architecture-governance/references/ai-guidance.md
index 9cb1ea72..f07f6da4 100644
--- a/.agents/skills/architecture-governance/references/ai-guidance.md
+++ b/.agents/skills/architecture-governance/references/ai-guidance.md
@@ -1,6 +1,6 @@
# AI implementation guidance
-Architecture governance is a pre-code decision protocol. A coding agent should be able to answer these questions before producing a patch:
+Architecture governance is a pre-code decision protocol. Apply the rows relevant to the change: state/ordering questions apply to stateful behavior, and contract questions apply to boundary changes. A local fix with an established contract does not need a new design record. Use the root AGENTS.md for spec requirements and when user input is necessary.
| Decision | Required answer |
| -------- | ------------------------------------------------------------------------------------------------------------- |
@@ -10,7 +10,7 @@ Architecture governance is a pre-code decision protocol. A coding agent should b
| Layer | Which layer owns the new code, and which direction may imports travel? |
| Reuse | Which existing path already performs part of this work, and why is a new path necessary? |
| Time | What is the event order, idempotency key, stale-result rule, and retry boundary? |
-| Remote | Does this preserve desktop continuous delivery and mobile replayable recovery separately? |
+| Remote | Does this preserve desktop continuous delivery and ordinary Web/SSH replayable recovery separately? |
| Context | Which contracts, specs, and tests are sufficient for the agent to work without reading whole implementations? |
The agent should reject these shapes during design:
@@ -20,10 +20,10 @@ The agent should reject these shapes during design:
- a new cache, event bus, adapter, or helper that duplicates an existing path;
- a domain object importing filesystem, process, network, timer, or platform APIs;
- a cross-module deep import added only to avoid defining a contract;
-- a remote stream change that mixes desktop `continuous` and mobile `replayable` semantics;
+- a remote stream change that mixes desktop `continuous` and ordinary Web/SSH `replayable` semantics;
- a broad refactor that changes unrelated modules without an explicit migration boundary.
-The patch description should include a small decision record:
+For stateful or cross-boundary changes, capture the relevant decisions in the existing spec/contract or patch description; omit fields that do not apply:
```text
owner:
@@ -34,4 +34,4 @@ delivery:
contracts/spec/tests:
```
-This guidance complements executable rules. The policy checker can prove import and size constraints; the decision record makes ownership, reuse, and time semantics explicit before an agent writes code.
+This guidance complements executable rules. The policy checker verifies configured import and size constraints within its managed coverage; the decision record explains ownership, reuse, and time semantics that the checker cannot prove.
diff --git a/.agents/skills/architecture-governance/references/golden-module/CONTRACT.md b/.agents/skills/architecture-governance/references/golden-module/CONTRACT.md
index ec61ab5f..dddad9b4 100644
--- a/.agents/skills/architecture-governance/references/golden-module/CONTRACT.md
+++ b/.agents/skills/architecture-governance/references/golden-module/CONTRACT.md
@@ -1,3 +1,3 @@
# Golden module
-This fixture demonstrates the smallest managed module: a manifest, a narrow port, an example, and no implementation details in the public surface.
+This fixture demonstrates the smallest managed module: a manifest, a narrow port, an example, a contract test, and no implementation details in the public surface.
diff --git a/.agents/skills/architecture-governance/references/module-contract.md b/.agents/skills/architecture-governance/references/module-contract.md
index da338829..4b8686d4 100644
--- a/.agents/skills/architecture-governance/references/module-contract.md
+++ b/.agents/skills/architecture-governance/references/module-contract.md
@@ -2,4 +2,4 @@
A managed module exposes only its contract entrypoint. The contract contains branded identifiers, schemas, typed commands, events, errors, and the semantics required by callers. `domain` stays pure, `app` owns use-case orchestration, `adapters` owns IO and process boundaries, and `ui` consumes the module port/read model.
-The example is part of the agent context package. The short `CONTRACT.md` records invariants that types cannot express.
+The example and contract test are part of the agent context package. The short `CONTRACT.md` records only invariants that types and tests cannot express.
diff --git a/.agents/skills/architecture-governance/references/policy-schema.md b/.agents/skills/architecture-governance/references/policy-schema.md
index 12266525..bdda3e67 100644
--- a/.agents/skills/architecture-governance/references/policy-schema.md
+++ b/.agents/skills/architecture-governance/references/policy-schema.md
@@ -1,9 +1,7 @@
# Policy schema
-`architecture-policy.yaml` contains `version: 1`, `modules`, global thresholds and optional exceptions. The current parser is `scripts/architecture/policy.mjs`.
+`architecture-policy.yaml` contains `version: 1`, a list of modules, global thresholds, and optional exceptions.
-Each module declares an `id`, one or more `roots`, optional `managed: true`, `requires`, `publicEntrypoints`, `layers`, `layerOrder` and `owner`. Existing legacy modules can remain unmanaged. Dependencies must name registered module IDs.
+Each module has an `id`, one or more `roots`, optional `managed: true`, `requires`, `provides`, `publicEntrypoints`, and an optional `owner`. Existing modules may remain unmanaged while they are in baseline migration.
-Global keys are `maxFileLines`, `maxContractLines`, `maxPublicMethods`, `forbidCycles`, `forbidDeepImports` and `managedOnly`. Layer names and ordering come from each module's configuration; do not assume a global layer list.
-
-The policy owns module topology. A managed module's `module.ts` declares local dependencies; keep it consistent with the policy. Verify supported options in the parser before documenting or using them.
+The policy owns path and layer topology. A module's `module.ts` owns its local dependency declaration. Do not add a second hand-maintained dependency graph.
diff --git a/.agents/skills/architecture-governance/references/rule-catalog.md b/.agents/skills/architecture-governance/references/rule-catalog.md
index 92701487..03250d59 100644
--- a/.agents/skills/architecture-governance/references/rule-catalog.md
+++ b/.agents/skills/architecture-governance/references/rule-catalog.md
@@ -1,20 +1,17 @@
# Rule catalog
-| Rule | Meaning | Typical fix |
-| -------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
-| `module-dependency` | Cross-module import is missing from `requires` | Add a public contract or move the integration owner |
-| `deep-import` | Import bypasses a module public entrypoint | Import the contract/index entrypoint |
-| `cycle` | The managed dependency graph contains a cycle | Split the owner or invert the dependency through a port |
-| `max-file-lines` | Managed source exceeds the policy budget | Split by responsibility; do not add a disable |
-| `max-contract-lines` | A contract is too broad | Split the capability or reduce the public surface |
-| `max-public-methods` | A contract exposes too many methods | Split the capability or introduce a narrower read/write contract |
-| `layer-direction` | A layer imports a higher implementation layer | Depend on a lower layer port or move the integration owner |
-| `domain-io` | Domain code imports process, network, filesystem, or timer APIs | Move IO to an adapter and pass a typed port into the domain |
-| `ui-implementation-import` | A file in a `ui` layer imports a repository, runtime, or service implementation | Depend on the module port / read model exposed by `contract.ts` |
-| `expired-exception` | A configured exception has expired | Resolve the violation and remove the expired exception |
-| `missing-module-artifact` | A managed module lacks its manifest or contract fixtures | Add the required module contract, example, test, and CONTRACT.md |
-| `disable-count` | Managed code adds a lint suppression | Fix the underlying violation or add a reviewed expiring exception |
+| Rule | Meaning | Typical fix |
+| -------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------- |
+| `module-dependency` | Cross-module import is missing from `requires` | Add a public contract or move the integration owner |
+| `deep-import` | Import bypasses a module public entrypoint | Import the contract/index entrypoint |
+| `cycle` | The managed dependency graph contains a cycle | Split the owner or invert the dependency through a port |
+| `max-file-lines` | Managed source exceeds the policy budget | Split by responsibility; do not add a disable |
+| `max-contract-lines` | A contract is too broad | Split the capability or reduce the public surface |
+| `max-public-methods` | A contract exposes too many methods | Split the capability or introduce a narrower read/write contract |
+| `layer-direction` | A layer imports a higher implementation layer | Depend on a lower layer port or move the integration owner |
+| `domain-io` | Domain code imports process, network, filesystem, or timer APIs | Move IO to an adapter and pass a typed port into the domain |
+| `ui-implementation-import` | UI imports a repository, runtime, or service implementation | Call a UI hook backed by the service contract |
+| `missing-module-artifact` | A managed module lacks its manifest or contract fixtures | Add the required module contract, example, test, and CONTRACT.md |
+| `disable-count` | Managed code adds a lint suppression | Fix the underlying violation or add a reviewed expiring exception |
Existing violations are suppressible only through `.architecture-baseline.json`; new violations remain blocking.
-
-The current resolver follows relative imports among discovered source files. Workspace package names, path aliases and dynamic imports need separate inspection. `--changed` selects working-tree differences from `HEAD` and untracked files; use a full check when reviewing committed changes.
diff --git a/.agents/skills/architecture-governance/references/troubleshooting.md b/.agents/skills/architecture-governance/references/troubleshooting.md
index 0ee2143f..793ba254 100644
--- a/.agents/skills/architecture-governance/references/troubleshooting.md
+++ b/.agents/skills/architecture-governance/references/troubleshooting.md
@@ -1,11 +1,6 @@
# Troubleshooting
-- `module-dependency`: use a public contract or declare the dependency in both the local manifest and policy after confirming its owner.
-- `deep-import`: use the target module's declared public entrypoint.
-- `cycle`: move shared types into a contract or invert the dependency through a port.
-- `missing-module-artifact`: add the managed module's manifest, contract, example or contract document reported by the check.
-- `expired-exception`: resolve the underlying violation and remove the expired exception; do not silently extend it.
-- Changed-file scope: `pnpm architecture:check --changed` selects differences from `HEAD` plus untracked files. Run `pnpm architecture:check` for a full scan; committed changes are no longer a dirty-worktree diff.
-- Baseline mismatch: inspect the failures first. Use `pnpm architecture:baseline:update` only for an explicitly reviewed baseline change.
-
-The check currently resolves relative imports. Inspect workspace aliases and package imports separately; a passing result does not prove that every dependency was analyzed.
+- `module-dependency`: import the target module's public contract, or add the dependency to `module.ts` and policy after confirming the integration owner.
+- `deep-import`: replace an internal path with the target module's declared public entrypoint.
+- `cycle`: move the shared type into a contract or create an application/integration owner; do not silence the cycle.
+- baseline mismatch: run the check without changing baseline, then use `architecture:baseline:update` only in a reviewed migration PR.
diff --git a/.agents/skills/dep-refs/SKILL.md b/.agents/skills/dep-refs/SKILL.md
index 7ffea950..dec8a88d 100644
--- a/.agents/skills/dep-refs/SKILL.md
+++ b/.agents/skills/dep-refs/SKILL.md
@@ -1,6 +1,6 @@
---
name: dep-refs
-description: Use when needs to inspect TypeScript export references in the ccbuddy workspace, list exports from a file, verify whether an export is unused before deletion, investigate who imports a symbol during refactors, or combine pnpm knip unused-export results with pnpm dep:refs symbol-level reference tracing.
+description: Use when the agent needs to inspect TypeScript export references in the ccbuddy workspace, list exports from a file, verify whether an export is unused before deletion, investigate who imports a symbol during refactors, or combine pnpm knip unused-export results with pnpm dep:refs symbol-level reference tracing.
disable-model-invocation: true
---
@@ -19,19 +19,19 @@ pnpm knip
Use `knip` to find likely unused exports, then inspect any risky or unclear export with `dep:refs`:
```bash
-pnpm dep:refs packages/shared/src/remoteTarget.ts:stripRemoteTargetSecrets
+pnpm dep:refs packages/services/src/bots/botsService.ts:createBotsService
```
List all exports in a file when the exact symbol name is unknown:
```bash
-pnpm dep:refs --list-exports packages/shared/src/remoteTarget.ts
+pnpm dep:refs --list-exports packages/services/src/bots/botsService.ts
```
Use scoped scans for fast exploration only when the scope is intentionally limited:
```bash
-pnpm dep:refs --scope packages/services packages/shared/src/remoteTarget.ts:stripRemoteTargetSecrets
+pnpm dep:refs --scope packages/services packages/services/src/bots/botsService.ts:createBotsService
```
Before claiming an export is safe to delete, prefer an unscoped `dep:refs` run so cross-package callers are not missed.
@@ -41,7 +41,7 @@ Before claiming an export is safe to delete, prefer an unscoped `dep:refs` run s
Use silent pnpm mode for machine-readable output, because normal `pnpm` output includes extra banner lines:
```bash
-pnpm -s dep:refs packages/shared/src/remoteTarget.ts:stripRemoteTargetSecrets --json | jq .
+pnpm -s dep:refs packages/services/src/bots/botsService.ts:createBotsService --json | jq .
```
Use JSON when summarizing many symbols, feeding results to `jq`, or comparing `references` and `reExports` counts programmatically.
diff --git a/.agents/skills/feature-boundary-planner/SKILL.md b/.agents/skills/feature-boundary-planner/SKILL.md
index 5bba3756..3f92b9cd 100644
--- a/.agents/skills/feature-boundary-planner/SKILL.md
+++ b/.agents/skills/feature-boundary-planner/SKILL.md
@@ -1,68 +1,177 @@
---
name: feature-boundary-planner
-description: Map a CCbuddy behavior change to current UI surfaces, state owners, protocol commands, persistence, and validation. Use for impact analysis, product-boundary planning, or an implementation handoff grounded in the checked-out source.
+description: Use when the agent needs to discover the blast radius of a CCbuddy feature or behavior change, answer "if I change X what other features or UI surfaces should I inspect?", map shared UI components, option builders, state owners, protocol commands, persistence and tests with codegraph, clarify product boundaries, prune state combinations, and prepare specs, case catalogs, coverage matrices, or E2E handoffs before implementation.
---
# Feature Boundary Planner
-Trace the requested behavior through the current checkout. A feature belongs in the result only when current source or an explicit new requirement supports it. Verify referenced paths and symbols before using them; do not reconstruct removed functionality from historical catalogs.
+Use this skill from the CCbuddy repository root before implementation when a request needs feature-impact discovery, product-boundary clarification, coverage planning, or an E2E handoff.
-## Choose The Scope
+Treat impact discovery and boundary planning as two connected jobs:
-- **impact-only:** inspect and report without changing code or product specs.
-- **planning:** establish behavior and acceptance cases before implementation.
-- **implementation-handoff:** turn confirmed decisions into a bounded implementation and validation plan.
+1. Determine where the capability appears and what a change can affect.
+2. Decide what the affected states should do and which combinations need coverage.
-Use the mode implied by the request. Clarify only unknown decisions that materially change the scope; do not ask the user to reconfirm facts already established in this task.
+Do not begin with a large Cartesian product. Build a compact semantic impact map, verify it against live code, then ask only the questions that change the result.
-## Find Current Evidence
+## Choose The Operating Mode
-1. Search aliases and node IDs in [ccbuddy-feature-graph.yaml](references/ccbuddy-feature-graph.yaml) for the user's terms. Read only matched nodes and their one-hop relationships, then verify the declared files, symbols and semantics against the current checkout. The graph is a curated seed index, not a complete feature inventory. Use [source-discovery.md](references/source-discovery.md) to fill gaps or start when there is no match. Read the relevant existing contracts and package scripts; read `DESIGN.md` for UI work and `CONTEXT.md` for plugin-store work.
-2. Locate the entrypoint with `rg --files` and focused `rg -n` searches. Trace direct callers with `pnpm dep:refs :` when the symbol is a TypeScript export. If an indexed codegraph tool is available, use it as additional evidence and verify its paths against the checkout.
-3. Classify the change: presentation, option source, draft/default, validation, commit effect, persistence, or recovery.
-4. Trace each user surface separately through validation and the command that commits the change. Shared UI components do not establish shared state or side effects.
-5. Identify the authoritative owner, derived views, protocol boundary, persistence and failure behavior. Name the semantic reason for each upstream or downstream dependency; imports alone do not prove a product relationship.
-6. Inspect one meaningful semantic hop first, expanding only when an unresolved owner or caller requires it. Rank relationships as must-inspect, should-inspect, conditional, invariant-only, or evidence-only.
+- `impact-only`: Produce an Impact Brief. Do not change product code or existing product specs.
+- `planning`: Produce the Impact Brief, clarify product semantics, and update specs or planning documents before code.
+- `implementation-handoff`: Complete planning and prepare accepted cases for implementation or `e2e-case-lifecycle`.
-For stateful or asynchronous behavior, show a concise diagram:
+Infer the least expansive mode from the user's request. A request to scan, explain, diagnose, or report impact is `impact-only`. A request to change or build uses `planning` unless the user explicitly limits the work.
-```text
-user action → surface draft → validation → owner command → event / persistence
- └── derived UI projection
-```
+## Required Reading
-## Maintain The Seed Graph
+For every impact scan, read:
-Keep existing node IDs for unchanged semantic boundaries and add aliases for new terminology. Report missing seeds or changed relationships as `graph-drift-candidate`; static reachability alone does not establish a product dependency.
+- `docs/feature-impact-discovery.md`
+- `references/ccbuddy-feature-graph.yaml`
+- `references/impact-brief-template.md`
+- the relevant rows in `references/ccbuddy-domain-cross-products.md`
-In `impact-only` mode, report proposed graph changes without editing files. In planning or implementation, update only verified entries within the task's scope. Follow the [graph contract](../../../docs/skills/feature-boundary-graph.md): check YAML parsing, unique IDs, relationship endpoints and ranks, and tracked source paths and symbols. Do not restore missing historical docs or claim test coverage from a graph entry.
+For `planning` or `implementation-handoff`, also read:
-## Plan And Prune
+- `packages/formal-proof/README.md`
+- `references/case-planning-template.md`
+- the current feature spec, nearby case catalog, coverage matrix, decision worksheet, and testing workflow.
-In planning modes, write or update a feature spec before implementation. Create the spec directory if needed; do not assume an existing case catalog, test runner, or coverage workflow.
+When conversation/session behavior is affected, read:
-Select only dimensions that can change behavior: state, event, target, client mode, delivery kind, workspace identity, runtime availability and persistence source. Prefer representative and high-risk combinations over a global Cartesian product.
+- `docs/conversation-protocol-declaration.md`
+- `docs/conversation-product-state-space.md`
+- `docs/conversation-session-case-catalog.md`
+- `docs/testing/conversation-session-e2e-coverage-matrix.md`
+- `docs/testing/conversation-session-e2e-development-workflow.md`
+- `docs/testing/conversation-session-requirement-matrix-workflow.md`
-Classify cases as accepted, undefined, pruned, ignored, or bug-candidate. Give every pruned case an invariant or guard; leave undefined behavior as a concrete decision. Record accepted cases with setup, action, assertions and required evidence using [case-planning-template.md](references/case-planning-template.md).
+Read only the domain-specific references routed by the feature graph and domain map. Prefer current fact/architecture docs over historical plans.
-Before handing off validation, check which tests, fixtures and commands actually exist in the target package. Distinguish planned tests, executed tests and admitted regression coverage. A missing test path is a gap, not coverage.
+## Classify The Change Layer
-## Output
+Classify the proposed change before expanding the graph. Use one or more of:
-Use [impact-brief-template.md](references/impact-brief-template.md) for the relevant parts of the result:
+- `presentation`: labels, layout, visibility, interaction controls, responsive behavior, theme, or locale.
+- `option-source`: candidates, grouping, sorting, filtering, registry, or discovery.
+- `draft-default`: initial value, inheritance, fallback, or local edit state.
+- `validation`: availability, permissions, guards, blocking, or error presentation.
+- `commit-effect`: command/service invoked and the runtime state changed by the action.
+- `persistence`: saved representation, load, migration, isolation key, or cache.
+- `recovery`: queue, snapshot, reconnect, replay, owner command, or stream recovery.
-- behavior summary and scope;
-- UI surface matrix, shared implementation and divergent behavior;
-- ranked dependencies with current file/symbol evidence;
-- state owners, validation points, commit commands and persistence;
-- invariants and representative validation cases;
-- unresolved decisions, unavailable evidence and relevant graph drift or updates.
+Do a low-cost scan before asking the user to classify an ambiguous change. Ask only when two interpretations would materially change the impact conclusion.
-## Boundaries
+## Discover Feature And UI Impact
-- Preserve `workspaceIdentity?.trim() || workspacePath` for isolation and `workspacePath` for execution/display.
-- Keep desktop `desktop-continuous` delivery separate from mobile `web-remote-replayable` recovery.
-- Trace accepted commands to their authoritative owner; do not turn a client draft or optimistic overlay into another accepted queue.
-- Treat runtime state, snapshots, indexes, settings and caches as distinct until their synchronization is proven.
-- Do not infer current functionality from a directory left behind by build artifacts, an old document, or a historical branch.
-- Do not silently change product semantics to match an implementation discrepancy; report the evidence and the decision needed.
+1. Match the user's terms to capability aliases in `references/ccbuddy-feature-graph.yaml`.
+2. Load the matched capability, its declared UI surfaces, shared implementation, state owners, invariants, docs, and one-hop semantic neighbors.
+3. Use codegraph before text search for indexed code:
+ - Explore every declared symbol/file seed.
+ - Find direct callers of shared components, option builders, hooks, stores, services, and protocol commands.
+ - Trace each UI surface through `local display/draft -> default/inheritance -> validation/gating -> commit command/service -> authoritative runtime or persistence owner`.
+ - Run impact analysis at depth 2 by default. Expand to depth 3 only when a critical owner or sink is still unresolved.
+ - Treat affected tests as evidence, not proof of a product dependency.
+4. Use `rg` only for docs, config, generated files, unindexed content, or precise string checks after the graph has identified the area.
+5. Compare live callers with declared surfaces. Record missing or stale declarations as `graph-drift-candidate`; do not silently rewrite product semantics from static reachability.
+6. Rank each relationship:
+ - `must-inspect`: shared edit point, owner, or commit path.
+ - `should-inspect`: shared option source, default, validation, or high-value invariant.
+ - `conditional`: relevant only for a change layer, client mode, workspace type, or runtime condition.
+ - `invariant-only`: no expected edit, but isolation must be verified.
+ - `evidence-only`: test or reachable implementation used only as supporting evidence.
+
+Never report every reachable node as an affected feature. Explain the semantic edge and why it matters for this change layer.
+
+## Build The UI Surface Matrix
+
+For every UI entry point, record:
+
+- user scenario and entry component;
+- shared component, option builder, hook, store, or service;
+- displayed value and local draft owner;
+- default, inheritance, and fallback source;
+- validation and gating;
+- commit command/service;
+- authoritative runtime owner or persistence sink;
+- desktop/web and local/remote differences;
+- behavior that must remain isolated from other surfaces.
+
+Shared UI does not imply shared behavior. The same `model` label can mean current conversation runtime, an automation record, a Subagent Markdown field, or a built-in override. Preserve those state and commit boundaries unless the user explicitly changes the product contract.
+
+## Clarify Product Boundaries
+
+After impact discovery, convert only undefined or conflicting relations into questions.
+
+- Ask 3-7 concrete questions per round.
+- Include small candidate answer sets and state what each answer includes, prunes, or leaves undefined.
+- Cover trigger, allowed state, target, forbidden side effects, isolation boundary, and proof evidence.
+- Summarize fixed boundaries and remaining unknowns after each answer batch.
+- Do not ask the user to reconfirm facts already established by code or current source-of-truth docs.
+
+If the user has not named any capability or behavior, ask one concise scope question before scanning.
+
+## Curate The Feature Graph
+
+Turn confirmed scan results into reusable retrieval data.
+
+- In `impact-only` mode, include a proposed graph delta in the Impact Brief but do not edit the graph.
+- In `planning` and `implementation-handoff` modes, update `references/ccbuddy-feature-graph.yaml` after the relevant product semantics are confirmed.
+- Add or update capability aliases, source docs, exact code seeds, UI surfaces, state owners, services, persistence nodes, delivery boundaries, typed edges, ranks, and conditions.
+- Use one UI surface node per distinct user scenario and state/commit contract. Put nested component call sites for that same surface into multiple `codeSeeds` instead of inventing duplicate product surfaces.
+- Keep node IDs stable. Add aliases when terminology changes; do not rename IDs merely to match new UI copy.
+- Persist only named product semantics. Leave raw reachability, tests, and unexplained callers as drift or evidence.
+- Re-run graph integrity and codegraph seed checks after every graph edit.
+
+## Enumerate And Prune
+
+Run this section only in `planning` or `implementation-handoff` mode.
+
+1. Select 1-4 primary domains and the high-risk cross-products that can change semantics.
+2. Extract only behavior-changing dimensions: product state, event, target, UI surface, client mode, delivery kind, workspace identity, model/provider config, runtime/network/filesystem/auth state, persistence source, and evidence layer.
+3. Enumerate with `state -> candidate -> guard -> effect -> case`. Prefer pairwise and high-risk combinations over global expansion.
+4. Present 3-7 candidate decisions at a time and prune with the user.
+5. Classify every candidate as `accepted`, `undefined`, `pruned`, `ignored`, or `bug-candidate`.
+6. Give every pruned case a guard/invariant reason. Give every undefined case a concrete question and candidate answers.
+7. Record accepted cases with setup, action, assertions, and evidence.
+
+## Write Docs Before Code
+
+In planning modes, update or create the feature spec, catalog, matrix, decision worksheet, or backlog before implementation. For conversation-related E2E work, update the conversation case catalog and coverage matrix and obtain pruning decisions before writing tests.
+
+Hand accepted E2E cases to `e2e-case-lifecycle` when they require manual-review specs, provider replay fixtures, Docker isolation, timing control, or CI promotion.
+
+## Output Contract
+
+Always use `references/impact-brief-template.md` and include:
+
+- feature summary and change-layer classification;
+- UI Surface Matrix;
+- shared implementation versus divergent behavior;
+- ranked upstream/downstream relationships;
+- state owners, validation points, commit sinks, and persistence;
+- must-preserve invariants;
+- codegraph seeds, direct callers, impact depth, and test evidence;
+- graph drift candidates;
+- confirmed or proposed graph delta;
+- unresolved questions that would change scope.
+
+For planning modes, append the sections in `references/case-planning-template.md`: clarification log, dimensions, candidate combinations, pruning decisions, accepted cases, coverage rows, and E2E handoff notes. Keep case IDs stable.
+
+## Guardrails
+
+- Static reachability is not a product dependency. Require a named semantic edge.
+- Shared components, hooks, labels, or fields do not prove shared state or commit effects.
+- Trace every UI surface to its own validation and commit sink.
+- Keep semantic expansion to one meaningful hop and codegraph impact to depth 2 by default; never start at depth 5.
+- Report graph drift for human confirmation instead of automatically declaring new product semantics.
+- Do not turn an `impact-only` request into code or product-spec edits.
+- Do not enumerate a large matrix before the first impact and clarification pass.
+- Do not guess undefined product behavior. Record the decision and ask the user.
+- Do not write E2E code until accepted semantics exist in docs.
+- Do not claim coverage from a path that lacks setup, action, and assertion.
+- Mark implementation/spec conflicts as `bug-candidate`; do not rewrite the contract to match current code without confirmation.
+- Keep desktop `desktop-continuous` and ordinary Web/SSH `web-remote-replayable` semantics separate.
+- Preserve workspace isolation with `workspaceIdentity?.trim() || workspacePath`; use `workspacePath` for execution and display.
+- Treat task sqlite index, session snapshots, runtime snapshots, localStorage, settings files, provider registry, and backend APIs as distinct sources until synchronization is proven.
+- Split environment faults, remote/replayable semantics, performance, telemetry, persistence migrations, and tool cross-products into specialized docs when they would obscure the main feature path.
diff --git a/.agents/skills/feature-boundary-planner/references/case-planning-template.md b/.agents/skills/feature-boundary-planner/references/case-planning-template.md
index 31f98f94..7f5d9085 100644
--- a/.agents/skills/feature-boundary-planner/references/case-planning-template.md
+++ b/.agents/skills/feature-boundary-planner/references/case-planning-template.md
@@ -91,5 +91,5 @@ Use this shape when creating a feature-specific decision or coverage-planning do
- Provider fixture:
- File-system fixture:
- Timing strategy:
-- Execution environment and available runner:
+- Docker preset:
- Review risks:
diff --git a/.agents/skills/feature-boundary-planner/references/ccbuddy-feature-graph.yaml b/.agents/skills/feature-boundary-planner/references/ccbuddy-feature-graph.yaml
index 0fa0b3e0..5c339122 100644
--- a/.agents/skills/feature-boundary-planner/references/ccbuddy-feature-graph.yaml
+++ b/.agents/skills/feature-boundary-planner/references/ccbuddy-feature-graph.yaml
@@ -1,600 +1,8219 @@
-# Maintainer contract: docs/skills/feature-boundary-graph.md
-# Search aliases and IDs first; read only the relevant nodes and one-hop relationships.
schemaVersion: 1
-graphId: ccbuddy-feature-relationships
-purpose:
- Curated source discovery seeds, not an exhaustive feature catalog or a product authority. Verify
- matched nodes and relationships against the current checkout.
+graphId: "ccbuddy-feature-relationships"
+purpose: "Seed semantic feature-impact discovery; verify every result against the current codegraph index."
+
rules:
- semanticExpansion:
- Load matched nodes and one meaningful semantic hop; expand only for unresolved owners
- or callers.
- driftPolicy:
- Report missing paths, symbols or changed semantics as graph-drift-candidate; do not infer
- product rules from reachability.
- stableIdPolicy:
- Keep existing IDs when the semantic boundary is unchanged; use aliases for terminology
- changes.
- persistencePolicy:
- Impact-only reports proposed graph changes. Planning or implementation may update
- verified entries within the requested scope.
+ semanticExpansion: "Follow one meaningful product edge by default."
+ codegraphImpactDepth: 2
+ maximumCodegraphImpactDepth: 3
+ driftPolicy: "Report live callers missing from this graph as graph-drift-candidate; require human confirmation before adding product semantics."
+ stableIdPolicy: "Keep node IDs stable; add aliases for terminology changes and multiple codeSeeds for nested call sites on the same product surface."
+ persistencePolicy: "In planning modes, write confirmed semantic nodes and edges back to this graph; in impact-only mode, report a proposed graph delta."
relationRanks:
- - must-inspect
- - should-inspect
- - conditional
- - invariant-only
- - evidence-only
+ - "must-inspect"
+ - "should-inspect"
+ - "conditional"
+ - "invariant-only"
+ - "evidence-only"
+
nodes:
- - id: capability.model-selection
+ - id: "capability.permission-full-access"
+ kind: "capability"
+ label: "Approval full access updates task and pending input permissions"
+ aliases: ["完全访问", "审批权限同步", "fullAccess"]
+ docs:
+ [
+ "docs/permission-project-approval.md",
+ "docs/working-memory/provider-refactor/steps/todo-158-permission-dialog-full-access-and-queued-mode.md",
+ ]
+ codeSeeds:
+ - file: "apps/ccbuddy-cli/packages/core/src/runtime/permission-full-access.ts"
+ symbol: "grantPermissionFullAccess"
+ - file: "packages/ui/src/v4/composer/composerPermissionGrant.ts"
+ symbol: "applyComposerPermissionGrant"
+ - id: surface.permission-full-access
+ kind: ui-surface
+ label: Shared desktop/Web approval full access action
+ codeSeeds:
+ - file: packages/ui/src/v4/V4InteractionDialogs.tsx
+ symbol: V4InteractionDialogs
+ - file: packages/ui/src/PermissionDialog.tsx
+ symbol: PermissionDialog
+ - id: capability.plugin-store-mode-order
+ kind: capability
+ label: Public plugin categories ordered by interface mode
+ aliases:
+ - 插件分类排序
+ - Code Work 插件排序
+ - pluginStoreOrder
+ - "@ 插件排序"
+ - + 插件排序
+ docs:
+ - docs/plugins/plugin-store-mode-order.md
+ - docs/plugin-marketplace-ui-ux.md
+ codeSeeds:
+ - file: packages/ui/src/settings/PluginStoreListView.tsx
+ symbol: PluginStoreListView
+ - file: packages/ui/src/settings/pluginStoreListing.ts
+ symbol: groupItemsByCategory
+ - file: packages/ui/src/hooks/usePluginStoreOrder.ts
+ symbol: usePluginStoreOrder
+ - file: packages/ui/src/mentions/providers/pluginsMentionProvider.ts
+ symbol: usePluginsMentionProvider
+ - file: packages/shared/src/pluginStoreOrdering.ts
+ symbol: sortPluginStoreEntries
+ - file: packages/services/src/ccbuddy-agent/pluginReferenceCatalogRequest.ts
+ symbol: requestPluginReferenceCatalog
+ - file: apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/plugin-reference-catalog.ts
+ symbol: getPluginReferenceCatalog
+ - file: packages/shared/src/pluginStoreOrder.ts
+ symbol: parsePluginStoreOrder
+ - file: packages/services/src/client-config/clientConfigService.ts
+ symbol: createClientConfigService
+ invariants:
+ - coding selects code and general selects work without requests; category membership remains marketplace-owned.
+ - Configured items precede default-sorted remainder; explicit other rank is allowed; featured, personal, search and installed ordering remain isolated.
+ - Window Local Host owns the independent public ClientConfigService; remote workspace attachments reuse it. Legacy config readers stay unchanged.
+ - Public config cache is partitioned by endpoint/version/platform; failed refresh keeps the last successful snapshot; late UI results cannot cross service or request generations.
+ - Retired guides maps to utilities in list and detail; legal is localized and only appears when it contains plugins.
+ - "@ and + share official category/plugin ordering, with personal entries retaining relative order afterward; fuzzy score remains primary. Display category cannot widen frozen Session identity or enable disabled plugins."
+ - Legacy referenceCatalog omits category; only method-not-found on referenceCatalogWithCategory falls back. Closed Pickers do not request config; mode changes only reorder locally.
+ - id: "capability.referrals-embedded"
+ kind: "capability"
+ label: "Rewards center embedded website"
+ aliases: ["奖励中心", "Rewards", "邀请好友"]
+ docs: ["docs/rewards-embedded.md"]
+ codeSeeds:
+ - file: "packages/ui/src/rewards/RewardsProvider.tsx"
+ symbol: "RewardsProvider"
+ - file: "packages/ui/src/rewards/RewardsWebview.tsx"
+ symbol: "RewardsWebview"
+ - file: "packages/shared/src/rewardsBridge.ts"
+ symbol: "installRewardsPageBridge"
+ invariants:
+ - "Current-account credentials stay inside trusted embedded origins; no token in URL or bridge events."
+ - "Website invitation records remain static until a server contract is supplied."
+ - id: capability.foreground-bash-output
+ kind: capability
+ label: Foreground Bash output viewport
+ aliases:
+ - 前台 Bash 实时输出
+ - Bash 五行输出
+ - Bash 吸底
+ docs:
+ - docs/bash-background-parity.md
+ codeSeeds:
+ - file: packages/ui/src/ToolCallBlocks/renderers/execute.tsx
+ symbol: ExecuteToolCallBlock
+ - file: packages/ui/src/ToolCallBlocks/renderers/ExecuteOutput.tsx
+ symbol: ExecuteOutput
+ - file: packages/ui/src/components/ui/scroll-fade-viewport.tsx
+ symbol: ScrollFadeViewport
+ - file: apps/ccbuddy-cli/packages/adapters/src/exec/bash-progress-poller.ts
+ symbol: subscribeBashOutputProgress
+ invariants:
+ - Only expanded details render output; running and final fit short content with a five-line maximum and no additional notices or file links.
+ - Follow bounded previews by default; scrolling up freezes one window and scrolling to the bottom resumes; no frontend polling or output history accumulation.
+ - CLI execution, background output query, and desktop/Web delivery boundaries are unchanged.
+ - id: "capability.background-bash-output"
+ kind: "capability"
+ label: "Background Bash live output details"
+ aliases: ["后台 Bash 输出", "后台终端进展", "background Bash logs"]
+ docs: ["docs/background-bash-output-details.md", "docs/bash-background-parity.md"]
+ codeSeeds:
+ - file: "packages/ui/src/v4/ConversationStatusPanel.tsx"
+ symbol: "RunningStatusItem"
+ - file: "packages/ui/src/app-shell/BackgroundBashOutputSidePane.tsx"
+ symbol: "BackgroundBashOutputSidePane"
+ - file: "packages/ui/src/hooks/useBackgroundBashOutput.ts"
+ symbol: "useBackgroundBashOutput"
+ - file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/background-bash-output.ts"
+ symbol: "readBackgroundBashOutput"
+ invariants:
+ - "Visible selected tab only; bounded 8 KiB tail; no model tool or runtime startup."
+ - "Frozen workspace/session/work identity; no output in replay events or persisted projections."
+ - id: "capability.interface-mode"
+ kind: "capability"
+ label: "General and coding interface modes"
+ aliases: ["通用模式", "编程模式"]
+ docs: ["docs/ui/general-coding-mode.md", "docs/testing/general-coding-mode-verification.md"]
+ codeSeeds:
+ - file: "packages/ui/src/lib/interfaceMode.ts"
+ symbol: "normalizeInterfaceMode"
+ - file: "packages/ui/src/hooks/useInterfaceMode.ts"
+ symbol: "useIsGeneralMode"
+ invariants:
+ - "Display preference does not change Agent capabilities or canonical task records."
+ - id: "state.interface-mode"
+ kind: "state"
+ label: "Renderer interface preference with persistence and broadcast"
+ codeSeeds:
+ - file: "packages/ui/src/store/index.ts"
+ symbol: "createCCbuddyStore"
+ - id: "surface.interface-mode-panels"
+ kind: "surface"
+ label: "Review and terminal creation entries with existing panels retained"
+ codeSeeds:
+ - file: "packages/ui/src/hooks/useAppPanels.ts"
+ symbol: "useAppPanels"
+ - id: capability.dangerous-command-approval
kind: capability
- label: Model selection
- aliases:
- - select model
- - model switch
- - model picker
- - 选择模型
- - 模型选择
- - 切换模型
- codeSeeds:
- - file: packages/provider/src/facades.ts
- symbol: ModelSelectionFacade
- - file: packages/ui/src/hooks/useModelSelectionView.ts
- symbol: useModelSelectionView
- invariants:
- - "Shared model controls do not share commit effects: conversation commands, automation records
- and Subagent configuration have separate owners."
- - id: surface.conversation-model-control
+ label: Guarded permission mode
+ aliases:
+ - guarded
+ - dangerousCommandRules
+ - 危险命令确认
+ - 强制单次确认
+ docs:
+ - docs/dangerous-command-approval-v1-plan.md
+ codeSeeds:
+ - symbol: matchDangerousCommand
+ file: apps/ccbuddy-cli/packages/core/src/tool/handlers/guarded/command.ts
+ - symbol: resolveToolPermission
+ file: apps/ccbuddy-cli/packages/core/src/tool/executor/permission-flow.ts
+ - symbol: buildProtocolPermissionOptions
+ file: apps/ccbuddy-cli/packages/bootstrap/src/permission-options.ts
+ - symbol: getCCbuddyAgentAvailableModes
+ file: packages/shared/src/ccbuddy-agent-model-state.ts
+ - symbol: buildAutomationModeOption
+ file: packages/ui/src/settings/automationAgentConfigOptions.ts
+ - symbol: filterModeOptions
+ file: apps/ccbuddy-cli/packages/tui/src/app-mode-command.ts
+ invariants:
+ - Only guarded mode explicit catalog matches add mandatory one-time approval; unsupported analysis retains YOLO policy. Existing modes and defaults are unchanged.
+ - Guarded replaces Edit only in selectable menus and shortcut cycles; persisted edit values and explicit legacy commands retain their original policy. YOLO and full-auto aliases remain unchanged.
+ - V1 targets bounded syntax in existing POSIX, Git Bash, and CMD shells; PowerShell payload analysis is out of scope and Bash AST must not stand in for CMD parsing.
+ - Deletion catalog includes recursive or syntactically batch rm, forced git clean, POSIX/Git Bash find -delete, CMD rd/rmdir /s and del/erase, and CMD/Git Bash robocopy MIR/PURGE unless reliably list-only. Quoted/escaped glob is not batch evidence; no filesystem expansion, indirect exec wrappers, or JOB file inspection.
+ - Matched input requires direct approval across guarded ingress and inherited children; no Hook auto-allow or project permission persistence.
+ - Ordinary and Explore subagents inherit guarded; dynamic-workflow subagents inherit the launching session's permission mode (guarded included) journaled when their run was created, and ask permissions and AskUserQuestion questions through the parent broker with a subagent origin; legacy workflow/script children remain YOLO while their guarded parent mode is preserved. Unattended guarded automation waits for approval.
+ - Approval remains bound to the actual input version; desktop continuous, Web/SSH replayable, and workspace identity remain isolated.
+ - id: "capability.marketing-touch"
+ kind: "capability"
+ label: "Marketing Touch card and popup delivery"
+ aliases: ["活动 banner", "营销弹窗", "远端 Hero", "Marketing Touch"]
+ docs: ["docs/marketing-touch-client.md", "docs/cloud-content-dialog.md"]
+ codeSeeds:
+ - file: "packages/ui/src/components/marketing-touch/MarketingTouchProvider.tsx"
+ symbol: "MarketingTouchProvider"
+ - id: "state.marketing-touch-controller"
+ kind: "state"
+ label: "Shell-owned marketing scheduling and action state"
+ codeSeeds:
+ - file: "packages/ui/src/components/marketing-touch/marketingTouchController.ts"
+ symbol: "createMarketingTouchController"
+ - id: "service.marketing-touch"
+ kind: "service"
+ label: "Authenticated delivery and confirm/cancel reports"
+ codeSeeds:
+ - file: "packages/services/src/marketing-touch/marketingTouchService.ts"
+ symbol: "createMarketingTouchService"
+ - id: "capability.delete-all-archived-tasks"
+ kind: "capability"
+ label: "Delete all archived tasks in the window"
+ aliases: ["一键删除所有归档任务", "批量删除归档任务", "归档更多菜单"]
+ docs: ["docs/task/delete-all-archived-tasks.md"]
+ codeSeeds:
+ - file: "packages/services/src/session/taskIndexRepo.ts"
+ symbol: "TaskIndexRepo"
+ invariants:
+ - "Confirm all window scopes; delete only targets still archived in the write transaction."
+ - "Keep CLI sessions and files; preserve workspace identity and existing source routing."
+ - id: "surface.desktop-archived-tasks"
+ kind: "ui-surface"
+ label: "Desktop archived task list"
+ codeSeeds:
+ - file: "packages/ui/src/WorkspaceSidebar.tsx"
+ symbol: "WorkspaceSidebar"
+ - file: "packages/ui/src/DeleteAllArchivedTasksButton.tsx"
+ symbol: "DeleteAllArchivedTasksButton"
+ - file: "packages/ui/src/WorkspaceArchivedTasksFlatSection.tsx"
+ symbol: "WorkspaceArchivedTasksFlatSection"
+ - id: surface.web-archived-tasks
kind: ui-surface
- label: Conversation composer model control
+ label: Web archived task list
+ codeSeeds:
+ - file: packages/ui/src/WorkspaceSidebar.tsx
+ symbol: WorkspaceSidebar
+ - file: packages/ui/src/DeleteAllArchivedTasksButton.tsx
+ symbol: DeleteAllArchivedTasksButton
+ - id: "capability.help-client-config"
+ kind: "capability"
+ label: "Community and feedback client configuration"
+ aliases: ["用户社群", "帮助配置", "feedbackUrl"]
+ docs: ["docs/ui/settings-community-link-config.md", "docs/testing/help-client-config-e2e.md"]
+ codeSeeds:
+ - file: "packages/shared/src/helpAppConfig.ts"
+ symbol: "createHelpAppConfigReader"
+ - file: "packages/desktop/src/main/desktopHelpConfig.ts"
+ symbol: "createDesktopHelpConfigReader"
+ - file: "packages/web/src/communityUrl.ts"
+ symbol: "resolveWebHelpConfig"
+ - id: "capability.subagent-computer-use-boundary"
+ kind: "capability"
+ label: "Subagent official Computer Use boundary"
+ aliases:
+ - "subagent CUA restriction"
+ - "子 Agent Computer Use 限制"
+ - "subagent computer use unavailable"
+ docs:
+ - "docs/cua/2026-08-18-subagent-computer-use-unavailable-spec.md"
+ - "apps/ccbuddy-cli/docs/design/v2/tool/07-subagent.md"
+ codeSeeds:
+ - symbol: "createBorrowedSubagentMcpAccess"
+ file: "apps/ccbuddy-cli/packages/core/src/subagent/borrowed-mcp-port.ts"
+ - symbol: "resolveSubagentMcpAccess"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/subagent.ts"
+ - symbol: "resolveSubagentSkillPort"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/subagent.ts"
+ - symbol: "main"
+ file: "apps/ccbuddy-cli/packages/node-repl-host/src/server.ts"
+ invariants:
+ - "Only authority-verified official CUA is restricted; third-party MCP/Skill is unchanged."
+ - "Main CUA and desktop/web delivery boundaries are unchanged."
+
+ - id: "capability.model-io-full-retention"
+ kind: "capability"
+ label: "ModelIO full retention diagnostics"
+ aliases:
+ - "modelio full retention"
+ - "full model io"
+ - "完整保留模型 I/O"
+ - "模型轨迹全量保留"
+ docs:
+ - "docs/model-io-full-retention-setting.md"
+ - "apps/ccbuddy-cli/docs/design/v2/model/README.md"
+ codeSeeds:
+ - symbol: "writeModelIODebugRecord"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-debug.ts"
+ - symbol: "AiSdkModelAdapter"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner.ts"
+
+ - id: "capability.agent-core-runtime"
+ kind: "capability"
+ label: "CCbuddy CLI agent core runtime"
+ aliases:
+ - "agent core"
+ - "turn runtime"
+ - "智能体核心"
+ - "回合运行时"
+ docs:
+ - "docs/product-capability-map.md"
+ - "apps/ccbuddy-cli/docs/design/v2/runtime-refactor.md"
+ - "apps/ccbuddy-cli/docs/design/v2/loop/architecture.md"
+
+ - id: "capability.read-pdf"
+ kind: "capability"
+ label: "Read PDF native input and page extraction"
+ aliases:
+ - "Read pages"
+ - "PDF Read"
+ - "PDF 页面提取"
+ docs:
+ - "apps/ccbuddy-cli/docs/design/v2/tool/01-read.md"
+ - "apps/ccbuddy-cli/docs/design/v2/tool/00-tool-change-chain.md"
+ codeSeeds:
+ - symbol: "readToolEntry"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/handlers/read.ts"
+ - symbol: "createPopplerPdfDocumentAdapter"
+ file: "apps/ccbuddy-cli/packages/adapters/src/pdf/index.ts"
+ - symbol: "persistToolResultMediaAttachments"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/helpers/tool-result-media-persistence.ts"
+ - symbol: "toAiSdkMessages"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/transform.ts"
+ invariants:
+ - "supportsPdf conditionally controls both provider-visible and executor Read schemas from the current turn snapshot."
+ - "Native PDF and extracted page images keep supportsPdf and supportsImages as independent capabilities."
+ - "Live and cold-resumed page images keep the same page order through the existing tool media artifact layout."
+ - "Images, extracted PDF pages, native PDFs, binary files, and videos share one 40MiB provider-request budget measured as full data URL bytes; per-file video input validation remains separate."
+
+ - id: "capability.browser-use-agent-guidance"
+ kind: "capability"
+ label: "Browser Use agent navigation, locator planning and popup recovery guidance"
+ aliases:
+ - "Browser Use explicit navigation wait"
+ - "DOMContentLoaded wait"
+ - "Browser Use locator planning"
+ - "snapshot-proven locator"
+ - "generic click target"
+ - "popup recovery"
+ - "浏览器显式导航等待"
+ - "浏览器智能体定位"
+ - "弹窗标签恢复"
+ docs:
+ - "docs/browser-use/2026-07-31-browser-generic-target-popup-recovery-guidance-spec.md"
+ - "docs/browser-use/2026-07-10-browser-use-codex-parity-spec.md"
+ - "docs/testing/browser-use-codex-parity-coverage-matrix.md"
+ codeSeeds:
+ - symbol: "loadBrowserDocumentation"
+ file: "apps/ccbuddy-cli/packages/core/src/browser-client/documentation.ts"
+ - symbol: "buildJsToolDescription"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/handlers/node-repl.ts"
+ - symbol: "TAB_CONTEXT_RECOVERY_HINT"
+ file: "packages/desktop/src/main/browserView/browserGuestManager.ts"
+ - file: "apps/ccbuddy-cli/packages/browser-use-plugin/docs/api.json"
+ - file: "apps/ccbuddy-cli/packages/browser-use-plugin/docs/overview.md"
+ - file: "apps/ccbuddy-cli/packages/browser-use-plugin/docs/playwright.md"
+ - file: "apps/ccbuddy-cli/packages/browser-use-plugin/docs/workflow.md"
+ - file: "apps/ccbuddy-cli/packages/browser-use-plugin/skills/control-browser/SKILL.md"
+
+ - id: "capability.browser-use-plugin-distribution"
+ kind: "capability"
+ label: "Browser Use official plugin versioning and distribution"
+ aliases:
+ - "Browser Use plugin version"
+ - "Browser Use official cache"
+ - "Browser Use SEA assets"
+ - "浏览器插件版本"
+ - "浏览器插件发布"
+ docs:
+ - "docs/browser-use/2026-08-14-browser-use-plugin-version-0-3-0-spec.md"
+ - "docs/browser-use/2026-08-06-browser-use-plugin-version-0-2-1-spec.md"
+ - "docs/browser-use/2026-08-02-browser-use-plugin-version-0-1-3-spec.md"
+ - "apps/ccbuddy-cli/docs/design/v2/plugin-compat.md"
+ - "docs/browser-use/2026-07-13-node-repl-mcp-runtime-spec.md"
+ codeSeeds:
+ - file: "apps/ccbuddy-cli/packages/browser-use-plugin/package.json"
+ - file: "apps/ccbuddy-cli/packages/browser-use-plugin/.ccbuddy-plugin/plugin.json"
+ - symbol: "OFFICIAL_PLUGIN_DEFINITIONS"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/official-plugin-definitions.ts"
+ - symbol: "OFFICIAL_BROWSER_USE_REQUIRED_SEED_PATHS"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/official-plugin-definitions.ts"
+ - symbol: "officialSeaPlugins"
+ file: "apps/ccbuddy-cli/packages/cli/scripts/sea-official-plugin-assets.mjs"
+ - symbol: "NODE_REPL_SERVER_VERSION"
+ file: "apps/ccbuddy-cli/packages/node-repl-host/src/tool-contract.ts"
+ - symbol: "REMOTE_AGENT_OFFICIAL_PLUGIN_INCLUDED_TOP_LEVEL_PATHS"
+ file: "packages/server/src/remote/ccbuddyAgentOfficialPluginAssets.ts"
+ - symbol: "REMOTE_AGENT_OFFICIAL_PLUGIN_REQUIRED_RELATIVE_PATHS"
+ file: "packages/server/src/remote/ccbuddyAgentOfficialPluginAssets.ts"
+ - file: "packages/desktop/scripts/prepare-agent-node-bundle.mjs"
+ - file: "scripts/prepare-prebuilds.mjs"
+
+ - id: "capability.model-capabilities"
+ kind: "capability"
+ label: "Model capabilities"
+ aliases:
+ - "model platform"
+ - "model config"
+ - "model catalog"
+ - "CCbuddy Built-in Model Config Rules"
+ - "模型能力"
+ - "模型平台"
+ - "模型配置"
+ - "模型启停"
+ - "Model ID 冲突"
+ - "GLM-x-preview-f"
+ - "x-preview-f-free"
+ docs:
+ - "docs/product-capability-map.md"
+ - "docs/working-memory/provider-refactor/steps/03-staging-integration.md"
+ - "docs/working-memory/provider-refactor/design/registry/configuration.md"
+ - "docs/working-memory/provider-refactor/design/registry/provider-and-model-configuration-overview.md"
+ - "docs/working-memory/provider-refactor/design/registry/request-compatibility-and-access-protocols.md"
+ - "docs/working-memory/provider-refactor/design/registry/model-membership-and-enablement.md"
+ - "docs/working-memory/provider-refactor/design/registry/ccbuddy-builtin-provider-config.md"
+ - "docs/working-memory/provider-refactor/design/model/input-output-format.md"
+ - "docs/working-memory/provider-refactor/design/model/model-option-map.md"
+ - "docs/working-memory/provider-refactor/design/model/option-map-writing-style.md"
+ - "docs/working-memory/provider-refactor/steps/todo-98-builtin-option-map-formatting-regression.md"
+ - "docs/working-memory/provider-refactor/steps/todo-95-builtin-reasoning-map-simplification-and-fallback.md"
+ - "docs/working-memory/provider-refactor/steps/todo-03-provider-model-ownership-and-property-totality.md"
+ - "docs/working-memory/provider-refactor/steps/todo-14-post-refactor-mechanical-zero.md"
+ - "docs/working-memory/provider-refactor/steps/todo-55-provider-template-inheritance-cutover.md"
+ - "docs/model-provider-opencode-config.md"
+ - "docs/model-request-output-tokens.md"
+ - "docs/superpowers/specs/2026-07-10-glm-52-suffixed-variant-media-fallback-design.md"
+ - "docs/superpowers/specs/2026-07-17-kimi-k3-model-adapter-design.md"
+ - "docs/superpowers/specs/2026-08-14-glm-5-3-model-adapter-design.md"
+ - "docs/superpowers/specs/2026-08-21-model-provider-modality-settings-design.md"
+ codeSeeds:
+ - symbol: "ModelInputFormatConfig"
+ file: "packages/provider/src/config/model-config.ts"
+ - symbol: "ModelConfigRules"
+ file: "packages/provider/src/config/model-config.ts"
+ - symbol: "compileModelOptionMaps"
+ file: "packages/model-option-map/src/option-maps.ts"
+ - symbol: "createModelOptionMapFetch"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/model-option-map-fetch.ts"
+ - symbol: "ProviderConfigResolver"
+ file: "packages/provider/src/resolver.ts"
+ - symbol: "ProviderRegistry"
+ file: "packages/provider/src/registry.ts"
+ - symbol: "ProviderSettingsFacade"
+ file: "packages/provider/src/facades.ts"
+ - symbol: "createProviderModelDraftValues"
+ file: "packages/ui/src/settings/model-provider-section/ProviderModelMetadata.ts"
+ - symbol: "ApiProviderModelRuntime"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/provider-registry-model-runtime.ts"
+ - symbol: "compactActiveConversation"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/compact-active.ts"
+ - symbol: "ccbuddySessionSettingsToCCbuddyConfigOptions"
+ file: "packages/shared/src/ccbuddy-agent-model-state.ts"
+ invariants:
+ - "Each complete Model Option Spec owns a restricted CEL map from its effective value to an RFC 7396 raw request-body patch. Config validation and runtime execution share @ccbuddy/model-option-map; the adapter applies reasoningLevel then maxOutputTokens after AI SDK serialization and schema compatibility but before body-dependent signing, so reasoningMapping, AI SDK reasoning namespaces, and duplicate max-output authorities are not model facts."
+
+ - id: "capability.model-selection"
+ kind: "capability"
+ label: "Model selection"
+ aliases:
+ - "select model"
+ - "model switch"
+ - "model picker"
+ - "选择模型"
+ - "模型选择"
+ - "切换模型"
+ docs:
+ - "docs/ui/chat-model-select-menu.md"
+ - "docs/chat/manual-model-switch-resolution-chain.md"
+ - "docs/chat/new-task-model-resolution-chain.md"
+ - "docs/working-memory/provider-refactor/steps/todo-12-model-selection-validation-boundary-cutover.md"
+ - "docs/working-memory/provider-refactor/steps/todo-13-target-host-model-selection-authority.md"
+ - "docs/working-memory/provider-refactor/steps/todo-14-post-refactor-mechanical-zero.md"
+ - "docs/working-memory/provider-refactor/design/interaction/selection-state.md"
+ - "docs/working-memory/provider-refactor/steps/todo-34-model-selection-async-state-and-repo-wiki-draft.md"
+ - "docs/working-memory/provider-refactor/steps/todo-68-active-model-context-single-source.md"
+ - "docs/working-memory/provider-refactor/steps/todo-87-selection-view-unified-resolution.md"
+ - "docs/working-memory/provider-refactor/steps/todo-95-builtin-reasoning-map-simplification-and-fallback.md"
+ - "docs/working-memory/provider-refactor/steps/todo-96-selection-offline-migration-and-resolution-consolidation.md"
+ - "docs/working-memory/provider-refactor/steps/todo-97-subagent-markdown-in-place-migration-and-legacy-read-audit.md"
+ - "docs/working-memory/provider-refactor/steps/todo-99-worker-account-selection-repair.md"
+ codeSeeds:
+ - symbol: "ModelSelectionFacade"
+ file: "packages/provider/src/facades.ts"
+ - symbol: "ApiProviderModelRuntime"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/provider-registry-model-runtime.ts"
+ - symbol: "useModelSelectionServiceView"
+ file: "packages/ui/src/hooks/useModelSelectionView.ts"
+ - symbol: "resolveEffectiveModelSelection"
+ file: "packages/provider/src/effective-model-selection.ts"
+ - symbol: "resolveAutomationSubmissionModelSelection"
+ file: "packages/desktop/src/host/automationModelSelection.ts"
+ - symbol: "createNodeModelSelectionFacade"
+ file: "packages/provider-node/src/model-selection-facade.ts"
+ - symbol: "resolveSubagentSelection"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/helpers/subagent-selection.ts"
+ invariants:
+ - "ModelSelectionFacade projects visible user candidates and change events only; authoritative selection validation occurs against the current process Registry when ModelFactory builds a Model. Internal products such as Off-Peak may preflight an exact hidden selection through the Registry, but that preflight never replaces final Model construction validation."
+ - "User-visible model candidates, option specs, and preferredSelection come atomically from the target Workspace Host: local workspaces use Local Host and remote workspaces use Remote Host, with no Base/Local fallback while a remote target is waiting or disconnected. App Recent is isolated by workspaceKey = workspaceIdentity?.trim() || workspacePath; the Renderer may prefer a still-selectable Recent over Host preferredSelection, while final execution validation remains in the target Worker Registry and ModelFactory."
+ - "ModelSelection stores provider/model identity and selected options. ModelFactory validates and freezes complete execution selections; it never fills missing reasoning. New default completion and an explicit user model-menu choice use the highest public level (Todo 88). Auxiliary low-cost calls explicitly use values[0], while invalid saved Session/Composer effective selections remain empty without fallback or empty-selection notifications."
+ - "ModelSelection is future Model-creation intent, not a current execution fact. Each model step creates one immutable Active Model and uses that same object for Context current-model identity, model-aware tool projection, request options, and the Adapter request. Guide creates a new model step; execution-scoped Off-Peak and inherited Subagents must not mix their Active Model with Session Selection."
+ - "Implemented (Todo 87/99): A target-host selection read accepts original intent and returns a caller-specific effectiveSelection. Account and Personal/Built-in updates recompute temporary results without overwriting intent; UI reads share subscriptions and bounded cancellable read recovery. Composer, Automation, and Bot consume this result at their own commit boundaries. Bound Bot reads the Session original selection, never a second Bot preference; explicit /model commits the complete Session selection immediately. Managed Worker explicit Subagents use the same local facade through dependency injection, including Workflow children; implicit Subagents inherit Parent and execution overrides take priority. Ordinary providers keep identity and account plans follow the current plan. Off-Peak keeps its original provider and Ticket; cross-account remapping is excluded. Frozen runs and history do not remap. No new send-time synchronization barrier or storage migration is introduced. Worker account receivedRevision acknowledges receipt, not Registry application."
+
+ - id: "capability.provider-templates"
+ kind: "capability"
+ label: "Built-in Provider Templates and Personal Provider inheritance"
+ aliases:
+ - "Provider Template"
+ - "provider template inheritance"
+ - "template-created provider"
+ - "供应商模板"
+ - "模板创建供应商"
+ docs:
+ - "docs/working-memory/provider-refactor/design/registry/configuration.md"
+ - "docs/working-memory/provider-refactor/design/registry/provider-and-model-configuration-overview.md"
+ - "docs/working-memory/provider-refactor/steps/todo-55-provider-template-inheritance-cutover.md"
+ - "docs/working-memory/provider-refactor/steps/todo-61-provider-template-picker-and-brand-metadata.md"
+ codeSeeds:
+ - symbol: "ProviderTemplateMap"
+ file: "packages/provider/src/config/provider-config.ts"
+ - symbol: "ProviderConfigResolver"
+ file: "packages/provider/src/resolver.ts"
+ - symbol: "ProviderSettingsFacade"
+ file: "packages/provider/src/facades.ts"
+ - symbol: "ProviderTemplatePicker"
+ file: "packages/ui/src/settings/model-provider-section/ProviderTemplatePicker.tsx"
+ - symbol: "resolveBuiltinProviderLogoAsset"
+ file: "packages/ui/src/settings/model-provider-section/ProviderLogo.tsx"
+ invariants:
+ - "A Provider Template is an inheritance baseline, not a Provider: it has a templateId but no providerId, group, Registry identity, execution state, or recursive Template parent."
+ - "A Template-created Personal Provider has its own providerId and resolves Template -> Personal; runtime execution uses only the resulting Effective Provider identity, API, Access, and Model Config."
+ - "Account Providers remain concrete Built-in Provider roots and are constrained by Account Config; ordinary API services are Templates that create Personal Provider instances."
+ - "ProviderTemplate is a domain shell containing localized name metadata and a Provider Config Overlay; Model Config Rules remain centralized so their precedence stays explicit."
+ - "Provider branding is declared once by Provider Config.logo; the UI logo resource table maps open built-in keys to packaged theme assets and unknown keys fall back without affecting executability."
+
+ - id: "capability.plan-entitlements"
+ kind: "capability"
+ label: "Plan identity, entitlements, quota, and billing"
+ aliases:
+ - "Coding Plan"
+ - "Team Plan"
+ - "Start Plan"
+ - "套餐权益"
+ - "套餐额度"
+ docs:
+ - "docs/usage-stats-app-coding-plan-split.md"
+ - "docs/zai-start-plan-provider.md"
+ - "docs/model-provider-family-merge.md"
+ - "docs/input-model-provider-team-plan-linkage.md"
+ - "docs/working-memory/provider-refactor/steps/todo-32-team-plan-settings-effective-provider-boundary.md"
+ - "docs/working-memory/provider-refactor/steps/todo-51-provider-access-consumer-and-offpeak-auth-closeout.md"
+ codeSeeds:
+ - symbol: "ModelProviderSectionDetail"
+ file: "packages/ui/src/settings/model-provider-section/Detail.tsx"
+ - symbol: "resolveTeamScopedPlanNavItem"
+ file: "packages/ui/src/settings/model-provider-section/Detail.tsx"
+ - symbol: "appendSubscribedTeamPlanItems"
+ file: "packages/ui/src/settings/model-provider-section/providerFamilyConnectionVisibility.ts"
+ - symbol: "useCodingPlanEntitlements"
+ file: "packages/ui/src/settings/model-provider-section/useCodingPlanEntitlements.ts"
+ - symbol: "buildVisibleFamilyConnectionItems"
+ file: "packages/ui/src/settings/model-provider-section/providerFamilyConnectionVisibility.ts"
+ - symbol: "resolveLatestModelProviderFamilyConnectionSelection"
+ file: "packages/ui/src/lib/modelProviderFamilyConnectionSelection.ts"
+ invariants:
+ - "Team Plan entitlement, quota, credential, and error state controls only the plan status card and related actions. Provider Settings renders Provider and Model facts from ProviderSettingsView effectiveConfig; entitlement status never hides an existing Effective Provider or causes Renderer to reapply Account Overlay."
+ - "An active Start Plan remains independently queryable and selectable alongside personal or Team Coding Plan."
+ - "Start remains visible in settings regardless of entitlement; executable models follow Registry availability. No inactive Start silently falls back to paid quota. Recommendation shares quota identity and requires a successful sample within 60 seconds, including side first-input submission."
+
+ - id: "capability.app-usage"
+ kind: "capability"
+ label: "App Usage analytics from CCbuddy CLI usage facts"
+ aliases:
+ - "App Usage"
+ - "usage stats"
+ - "应用用量"
+ - "用量统计"
+ docs:
+ - "docs/product-capability-map.md"
+ - "docs/usage-stats-app-coding-plan-split.md"
+ - "apps/ccbuddy-cli/docs/design/v2/usage-observability.md"
+
+ - id: capability.data-observability
+ kind: capability
+ label: Product telemetry, runtime observability, and monitoring
+ aliases:
+ - telemetry
+ - ARMS
+ - monitoring
+ - 埋点
+ - 监控
+ - 可观测性
+ docs:
+ - docs/product-capability-map.md
+ - docs/monitoring/performance-telemetry-catalog.md
+ - docs/monitoring/business-monitoring.md
+ - docs/monitoring/event-report-delivery-reliability.md
+ - docs/monitoring/ccbuddy-cli-resource-telemetry.md
+ - docs/monitoring/process-resource-telemetry.md
+ - docs/monitoring/process-resource-telemetry-fixes.md
+ - docs/monitoring/ccbuddy-data-size-arms-telemetry.md
+ - docs/monitoring/mcp-process-arms-telemetry.md
+ - docs/monitoring/remote-usage-telemetry-v1.md
+ - docs/trace/model-attempt-error-diagnostics.md
+ codeSeeds:
+ - symbol: sanitizeTelemetryError
+ file: apps/ccbuddy-cli/packages/telemetry/src/error-sanitizer.ts
+ - symbol: TerminalStreamChunkError
+ file: apps/ccbuddy-cli/packages/adapters/src/model/runner-retry.ts
+ - symbol: createCCbuddyProcessResourceSampler
+ file: apps/ccbuddy-cli/packages/bootstrap/src/process-resource-sampler.ts
+ - symbol: createCCbuddyAgentService
+ file: packages/services/src/ccbuddy-agent/ccbuddyAgentService.ts
+ - symbol: ingestCliResourceSample
+ file: packages/desktop/src/main/processResourceCliSource.ts
+ - symbol: registerHostServiceResourceTelemetry
+ file: packages/desktop/src/host/hostServiceResourceTelemetry.ts
+ - symbol: createMcpTelemetryTracker
+ file: apps/ccbuddy-cli/packages/adapters/src/mcp/telemetry.ts
+ - symbol: reportMcpTelemetryToArms
+ file: packages/desktop/src/main/desktopMcpTelemetry.ts
+ invariants:
+ - Resource telemetry stays outside desktop continuous and Web/SSH replayable task delivery.
+ - Host runtime environment identity isolates equal-hardware remote samples; identity is never an ARMS property.
+ - Only a Server advertising processResourceTelemetry receives resource subscriptions; old Server connections remain usable.
+ - Bash completion tokens are deduplicated at the main resource outlet across Host processes and omitted from ARMS.
+
+ - id: capability.renderer-user-action-trace
+ kind: capability
+ label: Desktop Renderer semantic user action trace
+ aliases:
+ - renderer action trace
+ - ui_action
+ - Renderer 用户操作 Trace
+ docs:
+ - docs/trace/desktop-renderer-user-action-trace.md
+ codeSeeds:
+ - symbol: USER_ACTION_CATALOG
+ file: packages/ui/src/lib/userActionTraceCatalog.ts
+ - symbol: RendererUserActionTelemetry
+ file: packages/ui/src/lib/userActionTelemetry.ts
+ - symbol: createRendererActionTraceBroker
+ file: packages/desktop/src/main/rendererActionTraceBroker.ts
+ invariants:
+ - Only Electron Desktop Renderer creates ui_action spans; Web remain no-op.
+ - Host, CLI, RPC and CCbuddy Protocol receive no trace changes.
+ - Product /event/report remains unchanged and separate.
+
+ - id: "capability.ccbuddy-data-size-telemetry"
+ kind: "capability"
+ label: "CCbuddy local data directory size telemetry"
+ aliases:
+ - "CCbuddy data size"
+ - "disk usage telemetry"
+ - "CCbuddy 数据目录占用"
+ - "磁盘占用埋点"
+ docs:
+ - "docs/monitoring/ccbuddy-data-size-arms-telemetry.md"
+ - "docs/monitoring/performance-monitoring.md"
+ codeSeeds:
+ - symbol: "createCCbuddyDataSizeTelemetryScheduler"
+ file: "packages/desktop/src/main/desktopCCbuddyDataSizeTelemetry.ts"
+ - symbol: "scanCCbuddyDataDirectory"
+ file: "packages/desktop/src/main/ccbuddyDataSizeScanner.ts"
+ - symbol: "scanCCbuddyDataDirectoryInWorker"
+ file: "packages/desktop/src/main/ccbuddyDataSizeWorkerClient.ts"
+ - symbol: "writeCCbuddyDataSizeTelemetryState"
+ file: "packages/desktop/src/main/ccbuddyDataSizeTelemetryState.ts"
+ - symbol: "getCCbuddyDataRootDir"
+ file: "packages/services/src/paths.ts"
+ invariants:
+ - "Measure only the local getCCbuddyDataRootDir logical byte size; never report paths, file names, workspace ids or remote targets."
+ - "Directory traversal runs in a Worker and stays outside conversation continuous/replayable delivery."
+ - "A running CCbuddy task always blocks collection; successful reports are limited to one per device per 24 hours."
+
+ - id: "capability.resource-manager"
+ kind: "capability"
+ label: "Resource Manager window (CPU / memory by base services, built-in plugins, community plugins)"
+ aliases:
+ - "资源管理器"
+ - "存储管理"
+ - "Storage management"
+ - "Resource Manager"
+ - "Process Monitor"
+ - "进程监视器"
+ - "CPU 内存占用"
+ - "网络请求"
+ - "Network requests"
+ docs:
+ - "docs/electron/resource-manager.md"
+ - "docs/electron/resource-manager-network.md"
+ - "docs/electron/process-naming.md"
+ - "docs/settings-storage-management.md"
+ - "docs/windows-caption-help-menu.md"
+ codeSeeds:
+ - symbol: "WorkspaceHelpMenuButton"
+ file: "packages/ui/src/WorkspaceHelpMenuButton.tsx"
+ - symbol: "buildResourceUsageSnapshot"
+ file: "packages/desktop/src/main/resourceManagerWindow.ts"
+ - symbol: "requestHostResourceUsage"
+ file: "packages/desktop/src/main/resourceManagerHostSampling.ts"
+ - symbol: "createHostResourceUsageResponder"
+ file: "packages/desktop/src/host/hostResourceUsage.ts"
+ - symbol: "attributeHostProcessTree"
+ file: "packages/services/src/process/processResourceSampler.ts"
+ - symbol: "collectLocalRuntimeChildProcesses"
+ file: "packages/services/src/ccbuddy-agent/ccbuddyAgentService.ts"
+ - symbol: "listChildProcesses"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/process-child-processes.ts"
+ - symbol: "registerResourceManagerStorageIpc"
+ file: "packages/desktop/src/main/resourceManagerStorage.ts"
+ - symbol: "startResourceManagerNetwork"
+ file: "packages/desktop/src/main/resourceManagerNetwork.ts"
+ - symbol: "LocalRuntimeNetworkCapture"
+ file: "packages/services/src/ccbuddy-agent/localRuntimeNetworkCapture.ts"
+ - symbol: "createStorageService"
+ file: "packages/services/src/storage/app/storageService.ts"
+ - symbol: "classifyStoragePath"
+ file: "packages/services/src/storage/domain/storageCatalog.ts"
+ invariants:
+ - "Electron main never spawns an external process for sampling; external process CPU/RSS is sampled inside the window Host only while a snapshot request is in flight."
+ - "Plugin attribution comes from the CLI process/childProcesses request (pid + mcpSource + pluginName); official marketplace MCP trees and browser-use guest renderers are built-in plugins, every other MCP tree is a community plugin."
+ - "CPU is machine-normalized (100% = all logical cores) for every row so group subtotals and the app total are additive."
+ - "The window is desktop-only and outside conversation continuous/replayable delivery; it must not change the 60s CLI resource telemetry cadence or MCP memory telemetry."
+ - "Network capture runs only for the resource-manager window lifetime, records bounded metadata from local Main/Host/App Renderer/CLI, and excludes MCP, subprocesses, remote CLI and external browser sessions without changing proxy routing."
+
+ - id: "capability.engineering-infrastructure"
+ kind: "capability"
+ label: "Engineering infrastructure outside the product runtime"
+ aliases:
+ - "engineering infrastructure"
+ - "developer productivity"
+ - "工程基础设施"
+ - "研发效能"
+ docs:
+ - "docs/product-capability-map.md"
+ - "docs/product-capability-map-engineering-infrastructure.md"
+
+ - id: "capability.build-release-engineering"
+ kind: "capability"
+ label: "Build and release engineering"
+ aliases:
+ - "CI/CD"
+ - "release pipeline"
+ - "packaging"
+ - "构建发布"
+ - "发版"
+ docs:
+ - "docs/product-capability-map-engineering-infrastructure.md"
+ - "docs/desktop/ccbuddy-preview-packaging.md"
+ codeSeeds:
+ - symbol: "runDesktopProductionBuild"
+ file: "packages/desktop/scripts/run-production-build.mjs"
+
+ - id: "capability.development-storage-isolation"
+ kind: "capability"
+ label: "Local development user-data isolation"
+ aliases:
+ - "development data root"
+ - "ccbuddy-dev"
+ - "dev storage profile"
+ - "开发态数据隔离"
+ - "开发数据库隔离"
+ docs:
+ - "docs/desktop/configurable-data-directory.md"
+ - "docs/testing/desktop-e2e-container.md"
+ codeSeeds:
+ - symbol: "resolveCCbuddyStorageProfile"
+ file: "packages/shared/src/storageProfile.ts"
+ - symbol: "applyEarlyDataBaseDirBootstrap"
+ file: "packages/desktop/src/main/desktopDataBaseDirBootstrap.ts"
+ - symbol: "resolveCCbuddyStorageRoot"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/ccbuddy-storage-root.ts"
+
+ - id: "capability.embedded-native-search"
+ kind: "capability"
+ label: "Embedded native search runtime"
+ aliases:
+ - "native search"
+ - "bfs"
+ - "ugrep"
+ - "embedded search"
+ - "原生搜索"
+ docs:
+ - "docs/runtime-tools/embedded-search-native-build.md"
+ - "docs/runtime-tools/ripgrep-runtime.md"
+ codeSeeds:
+ - symbol: "resolveNativeSearchPrebuiltPlan"
+ file: "scripts/native-search-tools-config.mjs"
+ - symbol: "buildRuntimeToolEnvPatch"
+ file: "packages/services/src/runtime-tools/runtimeToolResolver.ts"
+ - symbol: "buildEmbeddedSearchPreludeContent"
+ file: "apps/ccbuddy-cli/packages/adapters/src/exec/embedded-search-prelude.ts"
+
+ - id: "capability.test-automation-platform"
+ kind: "capability"
+ label: "Shared E2E and replay test automation platform"
+ aliases:
+ - "E2E"
+ - "test automation"
+ - "capture replay"
+ - "测试自动化"
+ - "回放测试"
+ docs:
+ - "docs/product-capability-map-engineering-infrastructure.md"
+ - "docs/testing/conversation-session-e2e-development-workflow.md"
+ - "docs/testing/desktop-e2e-container.md"
+ codeSeeds:
+ - symbol: "config"
+ file: "packages/desktop/wdio.conf.ts"
+ - symbol: "startConversationModelProviderReplayServer"
+ file: "packages/desktop/test/e2e/helpers/model-provider-replay.ts"
+
+ - id: "capability.quality-intelligence"
+ kind: "capability"
+ label: "Quality intelligence and engineering governance"
+ aliases:
+ - "quality data"
+ - "E2E report"
+ - "coverage governance"
+ - "质量数据"
+ - "覆盖率治理"
+ docs:
+ - "docs/product-capability-map-engineering-infrastructure.md"
+ - "docs/testing/desktop-e2e-reporting.md"
+ - "docs/testing/e2e-failure-artifact-report-upload.md"
+ codeSeeds:
+ - symbol: "createE2EReporter"
+ file: "packages/desktop/test/e2e/reporting/e2e-reporting.ts"
+ - symbol: "createE2EReportViewerUrl"
+ file: "packages/e2e-report/src/node/index.ts"
+
+ - id: "capability.office-computer-use-incubation"
+ kind: "capability"
+ label: "Office Suite and Computer Use incubation"
+ aliases:
+ - "Office Suite"
+ - "Office preview"
+ - "Office editing"
+ - "Computer Use"
+ - "办公套件"
+ - "文档预览编辑"
+ - "计算机操作"
+ docs:
+ - "docs/product-capability-map.md"
+ - "docs/product-capability-map-office-computer-use.md"
+
+ - id: "capability.office-suite"
+ kind: "capability"
+ label: "Office document preview, editing, and Agent production"
+ aliases:
+ - "DOCX"
+ - "XLSX"
+ - "PPTX"
+ - "PDF preview"
+ - "文档编辑"
+ - "表格编辑"
+ - "演示文稿编辑"
+ docs:
+ - "docs/product-capability-map-office-computer-use.md"
+ - "docs/document-skills-plugin.md"
+
+ - id: "capability.pptx-element-reference"
+ kind: "capability"
+ label: "PPTX preview element reference and verified OOXML update"
+ aliases:
+ - "PPTX element selection"
+ - "presentation element reference"
+ - "PPTX resolver"
+ - "PPTX 元素引用"
+ - "演示文稿元素选择"
+ docs:
+ - "docs/ui/pptx-preview.md"
+ - "docs/conversation-session-case-catalog.md"
+ codeSeeds:
+ - symbol: "PresentationPreviewDocument"
+ file: "packages/ui/src/presentation/types.ts"
+ - symbol: "serializeComposerPromptContexts"
+ file: "packages/ui/src/v4/composer/composerPromptContexts.ts"
+
+ - id: "capability.computer-use"
+ kind: "capability"
+ label: "Computer Use observation, input, task UX, and takeover"
+ aliases:
+ - "CUA"
+ - "ccbuddy-cua"
+ - "Computer Use plugin"
+ - "computer-use"
+ - "desktop control"
+ - "get_app_state"
+ - "open_application"
+ - "open app"
+ - "打开应用"
+ - "request_access"
+ - "CUA permission onboarding"
+ - "Read app interface"
+ - "读取应用界面"
+ - "电脑控制"
+ - "桌面操作"
+ docs:
+ - "docs/product-capability-map-office-computer-use.md"
+ - "docs/cua-permission-broker/ccbuddy-cua-permission-broker.md"
+ - "docs/cua-get-app-state-user-title.md"
+ - "docs/testing/cua-e2e-case-catalog.md"
+ - "docs/cua/2026-08-25-cua-request-access-onboarding-spec.md"
+ - "docs/superpowers/specs/2026-08-23-computer-use-plugin-display-name-spec.md"
+ codeSeeds:
+ - symbol: "resolvePluginDisplayName"
+ file: "packages/ui/src/settings/pluginStoreListing.ts"
+ - symbol: "OFFICIAL_PLUGIN_DEFINITIONS"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/official-plugin-definitions.ts"
+ - file: "apps/ccbuddy-cli/packages/ccbuddy-cua-plugin/skills/computer-use/SKILL.md"
+
+ - id: "capability.conversation-runtime"
+ kind: "capability"
+ label: "Conversation and session runtime"
+ aliases:
+ - "conversation"
+ - "chat"
+ - "session"
+ - "turn steer"
+ - "guide"
+ - "follow-up queue"
+ - "对话"
+ - "会话"
+ - "引导"
+ - "消息队列"
+ docs:
+ - "docs/conversation-protocol-declaration.md"
+ - "docs/conversation-product-state-space.md"
+ - "docs/chat/interaction-behavior.md"
+ - "docs/ui/chat-message-queue.md"
+ - "docs/architecture/message-flow.md"
+ - "docs/v4-refactor/17-cold-resume-runtime-activation.md"
+ codeSeeds:
+ - symbol: "ConversationV4Gateway.normalizeRuntimeEventSequence"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/v4-gateway.ts"
+ - symbol: "startPromptTurn"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/commands/prompt-turn.ts"
+ - symbol: "sendText"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/commands/handlers/session-flow.ts"
+ - symbol: "drainPendingInput"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/steering.ts"
+ invariants:
+ - "3.12.2: all new and cold-resumed runtimes normalize compatibility budget inputs to preflight-v1; runtime preferences never await client/configs for budgeting."
+
+ - id: "capability.selection-side-chat"
+ kind: "capability"
+ label: "Selection side conversation and App slash shortcuts"
+ aliases:
+ - "/side"
+ - "/btw"
+ - "selection side chat"
+ - "辅助对话"
+ docs:
+ - "docs/ui/conversation-selection-side-chat.md"
+ - "docs/conversation-session-case-catalog.md"
+ codeSeeds:
+ - symbol: "handleOpenSelectionSideConversation"
+ file: "packages/ui/src/v4/SessionPane.tsx"
+ - symbol: "createSelectionSideConversation"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/session-fork.ts"
+
+ - id: "capability.provider-retry-live-tail"
+ kind: "capability"
+ label: "Conversation provider retry live-tail presentation"
+ aliases:
+ - "apiRetry"
+ - "stream recovery retry status"
+ - "provider retry live tail"
+ - "模型重连状态"
+ - "重连次数"
+ docs:
+ - "docs/v4-composer-api-retry-status.md"
+ - "apps/ccbuddy-cli/docs/design/v2/model/stream-error-retry.md"
+ - "docs/conversation-session-case-catalog.md"
+ codeSeeds:
+ - symbol: "ChatApiRetryStatus"
+ file: "packages/ui/src/chat-input-toolbar/display.tsx"
+ - symbol: "TurnChatLoadingSlot"
+ file: "packages/ui/src/v4/ConversationTurnGroup.tsx"
+ - symbol: "ProductProjection"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/product-projection.ts"
+ - symbol: "ccbuddyApiRetryFromStreamRecoveryPayload"
+ file: "packages/shared/src/ccbuddy-api-retry-status.ts"
+
+ - id: "capability.model-stream-recovery"
+ kind: "capability"
+ label: "Model SSE recovery from a provider-safe anchor"
+ aliases:
+ - "partial output recovery"
+ - "reasoning-only retry"
+ - "thinking stall recovery"
+ - "SSE 断流恢复"
+ - "思考阶段重试"
+ docs:
+ - "apps/ccbuddy-cli/docs/design/v2/model/stream-error-retry.md"
+ - "apps/ccbuddy-cli/docs/design/v2/model/streaming-tool-execution-and-recovery.md"
+ - "docs/testing/conversation-session-environment-fault-catalog.md"
+ codeSeeds:
+ - symbol: "recoverPartialAssistantOutputFailure"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/streaming-recovery.ts"
+ - symbol: "createStreamingToolCoordinator"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/streaming-tool-coordinator.ts"
+
+ - id: "capability.model-empty-completion-retry"
+ kind: "capability"
+ label: "Model zero-output completion retry"
+ aliases:
+ - "empty model response retry"
+ - "zero-output completion retry"
+ - "空返回重试"
+ - "finish=other 空响应"
+ docs:
+ - "apps/ccbuddy-cli/docs/design/v2/model/stream-error-retry.md"
+ codeSeeds:
+ - symbol: "canRetryEmptyCompletion"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/empty-completion-retry.ts"
+ - symbol: "isZeroOutputModelCompletion"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-diagnostics.ts"
+ - symbol: "runGenerateText"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-generate.ts"
+ - symbol: "runStreamText"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-stream.ts"
+
+ - id: "capability.assistant-file-preview-links"
+ kind: "capability"
+ label: "Assistant file preview cards"
+ aliases:
+ - "Assistant Preview Cards"
+ - "回复文件预览卡片"
+ docs:
+ - "docs/ui/assistant-preview-cards.md"
+ codeSeeds:
+ - symbol: "buildAssistantPreviewCards"
+ file: "packages/ui/src/lib/assistantPreviewCards.ts"
+ - symbol: "extractAssistantFileReferences"
+ file: "packages/ui/src/lib/assistantFileReferences.ts"
+ - symbol: "resolveMarkdownFileLink"
+ file: "packages/ui/src/lib/markdownFileLink.ts"
+ - symbol: "useAssistantPreviewCardsForAssistantTextRow"
+ file: "packages/ui/src/v4/useAssistantPreviewCardsForRow.ts"
+ - symbol: "SessionPane"
+ file: "packages/ui/src/v4/SessionPane.tsx"
+ - id: "capability.session-residency-management"
+ kind: "capability"
+ label: "CLI session resident pool and cold deactivation"
+ aliases:
+ - "session resident pool"
+ - "session residency"
+ - "session deactivation"
+ - "session LRU"
+ - "会话常驻池"
+ - "会话内存回收"
+ - "会话去激活"
+ docs:
+ - "docs/session-idle-deactivation.md"
+ - "apps/ccbuddy-cli/docs/design/v2/session-idle-deactivation.md"
+ codeSeeds:
+ - symbol: "SessionResidentPool"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/session-resident-pool.ts"
+ - symbol: "deactivateSessionRecord"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/session-residency.ts"
+ - symbol: "hasResidencyBlockingWork"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/residency.ts"
+ - symbol: "acquireSessionResidencyFinalization"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/session-residency.ts"
+ - symbol: "clearSession"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/command-inbox.ts"
+
+ - id: "capability.ask-user-question-auto-resolution"
+ kind: "capability"
+ label: "AskUserQuestion auto-resolution preference"
+ aliases:
+ - "AskUserQuestion countdown"
+ - "ask user question auto continue"
+ - "提问自动继续"
+ - "提问自动倒计时"
+ docs:
+ - "docs/ask-user-question-auto-resolution.md"
+ - "docs/conversation-session-case-catalog.md"
+
+ - id: "capability.project-memory"
+ kind: "capability"
+ label: "CLI project Memory"
+ aliases:
+ - "Memory Extraction"
+ - "headless Memory Extraction gate"
+ - "project memory"
+ - "持久 Memory"
+ docs:
+ - "apps/ccbuddy-cli/docs/design/v2/memory/README.md"
+ codeSeeds:
+ - symbol: "MemorySettingsSection"
+ file: "packages/ui/src/settings/MemorySettingsSection.tsx"
+ - symbol: "IMemoryService"
+ file: "packages/services/src/memory/memory.ts"
+
+ - id: "capability.conversation-query-navigation"
+ kind: "capability"
+ label: "Complete conversation query navigation"
+ aliases:
+ - "conversation query navigator"
+ - "conversation query map"
+ - "turn navigator"
+ - "对话问题导航"
+ - "问题目录"
+ docs:
+ - "docs/ui/conversation-turn-navigator.md"
+
+ - id: "capability.markdown-image-gallery"
+ kind: "capability"
+ label: "Responsive Markdown image gallery and preview"
+ aliases:
+ - "markdown images"
+ - "image gallery"
+ - "Markdown 图片"
+ - "图片画廊"
+ docs:
+ - "docs/ui/markdown-preview-renderers.md"
+ codeSeeds:
+ - symbol: "MarkdownImage"
+ file: "packages/ui/src/components/ai-elements/markdown-image.tsx"
+ - symbol: "normalizeConsecutiveMarkdownImageBlocks"
+ file: "packages/ui/src/components/ai-elements/markdown-image.tsx"
+
+ - id: "capability.shared-image-preview"
+ kind: "capability"
+ label: "Shared conversation image gallery"
+ aliases:
+ - "BUA image preview"
+ - "Browser Use screenshot preview"
+ - "node_repl image lightbox"
+ - "message image thumbnails"
+ - "消息图片缩略图"
+ - "共享图片预览"
+ docs:
+ - "docs/ui/shared-image-preview-dialog.md"
+ codeSeeds:
+ - symbol: "ImagePreviewDialog"
+ file: "packages/ui/src/components/ai-elements/image-preview-dialog.tsx"
+ - symbol: "ImageThumbnailGallery"
+ file: "packages/ui/src/components/ai-elements/image-thumbnail-gallery.tsx"
+ - symbol: "NodeReplImageGrid"
+ file: "packages/ui/src/ToolCallBlocks/renderers/nodeReplImageGrid.tsx"
+
+ - id: "capability.browser-screenshot-surface"
+ kind: "capability"
+ label: "Browser Use background screenshot surface"
+ aliases:
+ - "BUA background screenshot"
+ - "browser screenshot surface"
+ - "backgroundThrottling"
+ - "浏览器后台截图"
+ - "后台截图表面"
+ docs:
+ - "docs/superpowers/specs/2026-07-23-browser-background-screenshot-surface-design.md"
+ - "docs/superpowers/specs/2026-07-24-browser-screenshot-negative-zoom-design.md"
+ - "docs/browser-use/2026-07-15-browser-turn-final-screenshot-spec.md"
+ codeSeeds:
+ - symbol: "BrowserGuestManager"
+ file: "packages/desktop/src/main/browserView/browserGuestManager.ts"
+ - symbol: "DesktopBrowserScreenshotSurfaceCoordinator"
+ file: "packages/desktop/src/main/browserView/browserScreenshotSurfaceCoordinator.ts"
+
+ - id: "capability.browser-webview-video-recording"
+ kind: "capability"
+ label: "Browser Use embedded WebView scenario video recording"
+ aliases:
+ - "Browser Use recording"
+ - "IAB video recording"
+ - "WebView screencast"
+ - "内置浏览器录屏"
+ - "WebView 视频录制"
+ docs:
+ - "docs/browser-use/2026-08-12-browser-webview-video-recording-spec.md"
+ - "docs/browser-use/2026-08-14-browser-use-plugin-version-0-3-0-spec.md"
+ - "docs/browser-use/browser-use-plugin-runtime-boundary.md"
+ codeSeeds:
+ - symbol: "BrowserGuestManager"
+ file: "packages/desktop/src/main/browserView/browserGuestManager.ts"
+ - symbol: "Tab"
+ file: "apps/ccbuddy-cli/packages/core/src/browser-client/facade.ts"
+ - symbol: "createBrowserControlMainBridge"
+ file: "packages/desktop/src/host/browserControlMainBridge.ts"
+ - symbol: "materializeBrowserRecordingArtifact"
+ file: "packages/desktop/src/host/browserRecordingArtifactMaterializer.ts"
+ - symbol: "recordBrowserVideo"
+ file: "packages/desktop/src/main/browserView/browserVideoRecorder.ts"
+
+ - id: "capability.embedded-browser-network-policy"
+ kind: "capability"
+ label: "Desktop renderer and embedded Browser explicit proxy and custom CA policy"
+ aliases:
+ - "embedded browser custom CA"
+ - "embedded browser proxy"
+ - "self-signed certificate in Browser"
+ - "内置浏览器自签名证书"
+ - "内置浏览器代理"
+ docs:
+ - "apps/ccbuddy-cli/docs/design/v2/model/http-proxy.md"
+ - "docs/testing/browser-use-codex-parity-coverage-matrix.md"
+ codeSeeds:
+ - symbol: "applyDesktopChromiumNetworkPolicies"
+ file: "packages/desktop/src/main/desktopNetworkPolicy.ts"
+ - symbol: "EMBEDDED_BROWSER_PARTITION"
+ file: "packages/desktop/src/main/browserDataManager.ts"
+ - symbol: "BrowserViewportSurface"
+ file: "packages/ui/src/browser-use/BrowserViewportSurface.tsx"
+
+ - id: "capability.host-api-network-policy"
+ kind: "capability"
+ label: "Window Host app-managed API explicit proxy, No Proxy, and custom CA policy"
+ aliases:
+ - "Host API proxy"
+ - "Host network egress"
+ - "Host OAuth proxy"
+ - "Host 外部 API 代理"
+ - "Host 网络出口"
+ docs:
+ - "docs/superpowers/specs/2026-08-25-host-api-proxy-design.md"
+ - "docs/architecture/external-api-client.md"
+ - "apps/ccbuddy-cli/docs/design/v2/model/http-proxy.md"
+ codeSeeds:
+ - symbol: "NodeApiClient"
+ file: "packages/services/src/providers/api/nodeApiClient.ts"
+ - symbol: "createLocalServices"
+ file: "packages/services/src/node.ts"
+ - symbol: "createRemoteWorkspaceServiceCollection"
+ file: "packages/desktop/src/host/remoteWorkspaceServiceCollection.ts"
+ invariants:
+ - "AppSettings is the only Host app-managed proxy, No Proxy, and custom CA authority; ambient shell proxy and CA variables remain sanitized."
+ - "The first phase injects a request-scoped transport into NodeApiClient and never installs a process-global dispatcher."
+ - "Desktop-attached remote app-global services use the desktop Host policy; server remote services retain server authority."
+ - "Conversation delivery, workspace identity, Agent/CLI, Electron Session, Bot, telemetry, object-upload, WebSocket, SSH, and RPC behavior remain unchanged."
+
+ - id: "capability.embedded-browser-responsive-viewport"
+ kind: "capability"
+ label: "Embedded browser free-size responsive viewport"
+ aliases:
+ - "browser free mode"
+ - "responsive browser viewport"
+ - "自由尺寸"
+ - "内置浏览器自由模式"
+ - "浏览器显示偏好"
+ - "恢复上次浏览器尺寸"
+ docs:
+ - "docs/browser-use/2026-07-15-responsive-browser-viewport-spec.md"
+ - "docs/testing/browser-use-codex-parity-coverage-matrix.md"
+ codeSeeds:
+ - symbol: "ResponsiveBrowserViewport"
+ file: "packages/ui/src/browser-use/ResponsiveBrowserViewport.tsx"
+ - symbol: "ResponsiveBrowserResizeHandles"
+ file: "packages/ui/src/browser-use/ResponsiveBrowserResizeHandles.tsx"
+ - symbol: "useResponsiveBrowserViewportControl"
+ file: "packages/ui/src/browser-use/useResponsiveBrowserViewportControl.ts"
+
+ - id: "capability.browser-tab-residency"
+ kind: "capability"
+ label: "Desktop embedded Browser 32-tab hard limit and process-local lifetime"
+ aliases:
+ - "browser tab hard limit"
+ - "logical tab limit"
+ - "browser tab limit"
+ - "browser tabs cleared on restart"
+ - "Browser 标签上限"
+ - "浏览器逻辑 Tab 上限"
+ - "内置浏览器 Tab 上限"
+ - "重启清空浏览器 Tab"
+ - "Browser Guest route residency"
+ - "自动化页后台 Guest"
+ docs:
+ - "docs/browser-use/2026-07-31-browser-tab-residency-budget-spec.md"
+ - "docs/browser-use/2026-07-12-browser-tab-process-lifetime-spec.md"
+ - "docs/browser-use/2026-07-14-browser-tab-conversation-isolation-spec.md"
+ - "docs/browser-use/2026-08-25-browser-guest-route-residency-spec.md"
+ codeSeeds:
+ - symbol: "BrowserGuestManager"
+ file: "packages/desktop/src/main/browserView/browserGuestManager.ts"
+ - symbol: "AnimatedSidePanePanel"
+ file: "packages/ui/src/app-shell/AnimatedSidePanePanel.tsx"
+ - symbol: "WorkspaceShellLayout"
+ file: "packages/ui/src/app-shell/WorkspaceShellLayout.tsx"
+
+ - id: "capability.office-file-preview"
+ kind: "capability"
+ label: "Office file preview"
+ aliases:
+ - "DOCX preview"
+ - "Word preview"
+ - "file preview"
+ - "DOCX 预览"
+ - "Office 文件预览"
+ docs:
+ - "docs/ui/office-file-preview.md"
+ codeSeeds:
+ - symbol: "getOfficeFilePreviewKind"
+ file: "packages/ui/src/lib/officeFilePreview.ts"
+ - symbol: "installDocumentLinkSafety"
+ file: "packages/ui/src/lib/officeFilePreview.ts"
+ - symbol: "PreviewPaneOfficeContent"
+ file: "packages/ui/src/previewPaneOfficeContent.tsx"
+ - symbol: "PdfViewer"
+ file: "packages/ui/src/components/ui/pdf-viewer.tsx"
+
+ - id: "capability.mcp-async-tool-catalog"
+ kind: "capability"
+ label: "MCP asynchronous tool catalog advertisement"
+ aliases:
+ - "MCP async initialization"
+ - "MCP tool catalog cache"
+ - "MCP warm startup"
+ - "MCP 异步初始化"
+ - "MCP 工具目录缓存"
+ docs:
+ - "docs/mcp-async-tool-catalog-cache.md"
+ - "docs/mcp-platform-boundary.md"
+ - "docs/runtime-tools/mcp-status-list-control-plane.md"
+ codeSeeds:
+ - symbol: "startMcpStartup"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/mcp.ts"
+ - symbol: "registerMcpTools"
+ file: "apps/ccbuddy-cli/packages/core/src/mcp/index.ts"
+
+ - id: "capability.mcp-dual-era-version-negotiation"
+ kind: "capability"
+ label: "MCP dual-era client version negotiation"
+ aliases:
+ - "MCP auto negotiation"
+ - "MCP server/discover fallback"
+ - "MCP legacy and modern compatibility"
+ - "MCP 双版本兼容"
+ - "MCP 自动协商"
+ docs:
+ - "docs/mcp-dual-era-version-negotiation.md"
+ - "docs/mcp-platform-boundary.md"
+ codeSeeds:
+ - symbol: "NodeMcpAdapter"
+ file: "apps/ccbuddy-cli/packages/adapters/src/mcp/index.ts"
+ - symbol: "resolveVersionNegotiationMode"
+ file: "apps/ccbuddy-cli/packages/adapters/src/mcp/index.ts"
+ - symbol: "ProcessTreeStdioClientTransport"
+ file: "apps/ccbuddy-cli/packages/adapters/src/mcp/stdio-transport.ts"
+
+ - id: "capability.stateless-browser-use-mcp"
+ kind: "capability"
+ label: "MCP 2026-07-28 stateless node_repl Browser Use"
+ aliases:
+ - "stateless browser use"
+ - "shared node_repl MCP"
+ - "stateless node repl"
+ - "Browser Use 无状态"
+ - "MCP 2026-07-28"
+ docs:
+ - "docs/mcp-stateless-node-repl.md"
+ - "docs/browser-use/2026-07-28-stateless-browser-use-mcp-spec.md"
+ - "docs/browser-use/browser-use-plugin-runtime-boundary.md"
+ codeSeeds:
+ - symbol: "createNodeReplMcpRuntime"
+ file: "apps/ccbuddy-cli/packages/node-repl-host/src/server.ts"
+ - symbol: "createNodeReplBrowserBroker"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/node-repl-browser-broker.ts"
+ - file: "apps/ccbuddy-cli/packages/node-repl-host/src/tool-contract.ts"
+ - file: "apps/ccbuddy-cli/packages/browser-use-plugin/skills/control-browser/SKILL.md"
+ - file: "apps/ccbuddy-cli/packages/contracts/src/tools/node-repl.ts"
+
+ - id: "capability.automations"
+ kind: "capability"
+ label: "Scheduled automations"
+ aliases:
+ - "automation"
+ - "scheduled task"
+ - "cron"
+ - "自动化"
+ - "定时任务"
+ docs:
+ - "docs/ui/scheduled-tasks-main-view.md"
+ - "docs/automation-edit-status.md"
+ - "docs/testing/automation-workspace-selection-boundary.md"
+
+ - id: "capability.off-peak-telemetry"
+ kind: "capability"
+ label: "Off-Peak creation, upgrade, and automatic-turn telemetry"
+ aliases:
+ - "off_peak_task_create_result"
+ - "off_peak_task_id"
+ - "session_idle_time"
+ - "coding_plan_upgrade_ck"
+ - "闲时任务埋点"
+ - "闲时任务升级埋点"
+ docs:
+ - "docs/monitoring/off-peak-task-telemetry-v1.md"
+ - "docs/off-peak-task/spec.md"
+ - "docs/coding-plan-purchase-funnel-telemetry.md"
+
+ - id: "capability.off-peak-model-execution"
+ kind: "capability"
+ label: "Off-Peak model execution"
+ aliases:
+ - "idle model"
+ - "idle plan model"
+ - "turnRuntimeModel"
+ - "executionOnly"
+ - "闲时模型"
+ - "闲时任务模型执行"
+ docs:
+ - "docs/working-memory/provider-refactor/steps/04-model-execution-cutover.md"
+ - "docs/working-memory/provider-refactor/design/execution/execution.md"
+ - "docs/working-memory/provider-refactor/design/registry/model-creation.md"
+ - "docs/working-memory/provider-refactor/design/registry/ccbuddy-builtin-provider-config.md"
+ - "docs/off-peak-task/spec.md"
+ codeSeeds:
+ - symbol: "buildOffPeakRequestAuth"
+ file: "packages/services/src/session/offPeakRuntimeModel.ts"
+ - symbol: "dispatchOffPeakRun"
+ file: "packages/desktop/src/host/index.ts"
+ - symbol: "sessionFlowHandlers"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/commands/handlers/session-flow.ts"
+ - symbol: "applySubmissionExecutionState"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/turn-model.ts"
+ - symbol: "ApiProviderModelRuntime"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/provider-registry-model-runtime.ts"
+ invariants:
+ - "Z.ai and BigModel each own one complete hidden Off-Peak Built-in Provider in the ordinary Registry. Account connection enables only the current family; neither Provider changes family at execution time, and visibility filters only Provider Settings and Model Selection."
+ - "3.12.2: supported local hosts advertise OffPeak tools without reading client/configs; OffPeakCreate validates gray configuration, plan and model in the Host handler before ticket/task creation. Page visibility and remote/subagent restrictions stay separate."
+ - "Off-Peak submissions use one standard ModelSelection and the ordinary ModelFactory; request auth, ticket, attribution, and child override are narrow execution context rather than a second model config."
+ - "An idle turn never writes or restores App Recent, Configured Default, or Session Selection, and never enters the ordinary busy queue."
+ - "Todo104 storage refinement: editing migrated Off-Peak tasks updates model_selection without clearing or back-writing the published model/thought_level columns; those columns remain rollback snapshots, not a second execution selection. New records need not synthesize legacy values."
+ - "Permission approval or denial continues the same automatic turn on the idle model; no provider handoff occurs."
+ - "Foreground children create their own Model from the idle override and request auth; background children are rejected before a model request."
+
+ - id: "capability.official-server-mcp"
+ kind: "capability"
+ label: "Official Server MCP authentication, quota, and unavailable banner"
+ aliases:
+ - "official MCP"
+ - "Server MCP"
+ - "CCbuddy MCP quota"
+ - "官方 MCP"
+ - "官方 MCP 额度"
+ docs:
+ - "docs/coding-plan/ccbuddy-official-server-mcp-auth-spec.md"
+ - "docs/coding-plan/ccbuddy-official-server-mcp-quota-banner-spec.md"
+ codeSeeds:
+ - symbol: "resolveOfficialMcpCredentials"
+ file: "packages/services/src/official-mcp/officialMcpCredentials.ts"
+
+ - id: "capability.mcp-settings-diagnostics"
+ kind: "capability"
+ label: "MCP Settings localized diagnostics and Plugin ownership grouping"
+ aliases:
+ - "MCP Settings errors"
+ - "MCP Plugin grouping"
+ - "MCP 设置页错误"
+ - "MCP 插件分组"
+ docs:
+ - "docs/settings-mcp-list.md"
+ - "docs/plugin-capabilities-settings.md"
+ - "docs/runtime-tools/mcp-status-list-control-plane.md"
+ codeSeeds:
+ - symbol: "McpSettingsSection"
+ file: "packages/ui/src/settings/McpSettingsSection.tsx"
+ - symbol: "mergeMcpServerStatusSnapshots"
+ file: "packages/ui/src/store/mcpStoreStatusList.ts"
+ - symbol: "listMcpServers"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/mcp.ts"
+ - symbol: "fetchMcpQuotaSnapshot"
+ file: "packages/services/src/usage-stats/providers/ccbuddyMcpQuotaProvider.ts"
+ - symbol: "useV4SessionQuotaBanner"
+ file: "packages/ui/src/v4/useV4SessionQuotaBanner.ts"
+
+ - id: "capability.subagents"
+ kind: "capability"
+ label: "Subagents"
+ aliases:
+ - "subagent"
+ - "agent profile"
+ - "子代理"
+ - "智能体配置"
+ docs:
+ - "docs/subagents-built-in-model-overrides.md"
+ - "docs/subagent-session-side-tabs.md"
+ - "docs/working-memory/provider-refactor/steps/todo-97-subagent-markdown-in-place-migration-and-legacy-read-audit.md"
+ - "docs/working-memory/provider-refactor/design/execution/execution.md"
+ - "docs/working-memory/provider-refactor/steps/04-model-execution-cutover.md"
+ - "docs/superpowers/plans/2026-07-06-subagent-permission-delegation.md"
+ codeSeeds:
+ - symbol: "AgentInputSchema"
+ file: "apps/ccbuddy-cli/packages/contracts/src/tools/agent.ts"
+ - symbol: "agentHandler"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/handlers/agent.ts"
+ - symbol: "createExploreSubagentPort"
+ file: "apps/ccbuddy-cli/packages/core/src/subagent/runner.ts"
+ - symbol: "migrateUserSubagentMarkdown"
+ file: "packages/shared/src/node/subagentMarkdownMigration.ts"
+ - symbol: "parseSubagentMarkdownSelection"
+ file: "packages/shared/src/subagent-markdown-selection.ts"
+ invariants:
+ - "Host and standalone Agent await offline migration of user-directory Markdown and builtin state before formal reads. Markdown keeps model/thoughtLevel; intermediate modelSelection is not consumed. Only user Markdown provider values are rewritten, preserving unrelated bytes; project/plugin files and symlinks are excluded. JSON uses builtInModelSelectionOverrides after one-way import. Ordinary readers do not remap legacy identities or fall back to old fields. Explicit execution still validates its concrete selection; Host effective-selection protocol remains a separate Todo96 decision."
+
+ - id: "capability.provider-registry"
+ kind: "capability"
+ label: "Model provider registry"
+ aliases:
+ - "provider"
+ - "custom provider"
+ - "provider registry"
+ - "CCbuddy Built-in Provider Config"
+ - "Account Provider Config"
+ - "Account Built-in Provider Config"
+ - "Effective Built-in Provider Config"
+ - "Provider Template"
+ - "Personal Provider Config"
+ - "Effective Provider Config"
+ - "模型供应商"
+ - "自定义 provider"
+ docs:
+ - "docs/working-memory/provider-refactor/steps/03-staging-integration.md"
+ - "docs/working-memory/provider-refactor/design/design.md"
+ - "docs/working-memory/provider-refactor/design/registry/configuration.md"
+ - "docs/working-memory/provider-refactor/design/registry/registry.md"
+ - "docs/working-memory/provider-refactor/design/registry/model-membership-and-enablement.md"
+ - "docs/working-memory/provider-refactor/design/registry/model-creation.md"
+ - "docs/working-memory/provider-refactor/steps/04-model-execution-cutover.md"
+ - "docs/working-memory/provider-refactor/steps/todo-03-provider-model-ownership-and-property-totality.md"
+ - "docs/working-memory/provider-refactor/research/model-abstraction-unification-cleanup-audit.md"
+ - "docs/working-memory/provider-refactor/steps/todo-00-model-abstraction-unification-roadmap.md"
+ - "docs/working-memory/provider-refactor/steps/todo-01-core-model-adapter-boundary-cleanup.md"
+ - "docs/working-memory/provider-refactor/steps/todo-02-ai-sdk-execution-registry-retirement.md"
+ - "docs/working-memory/provider-refactor/steps/todo-04-core-model-connection-port-retirement.md"
+ - "docs/working-memory/provider-refactor/steps/todo-05-model-ref-retirement.md"
+ - "docs/working-memory/provider-refactor/steps/todo-07-account-connection-selection-and-access-context-cutover.md"
+ - "docs/working-memory/provider-refactor/steps/todo-10-ccbuddy-builtin-provider-config-naming-cutover.md"
+ - "docs/working-memory/provider-refactor/steps/todo-12-model-selection-validation-boundary-cutover.md"
+ - "docs/working-memory/provider-refactor/steps/todo-13-target-host-model-selection-authority.md"
+ - "docs/working-memory/provider-refactor/steps/todo-20-account-overlay-atomicity-and-review-closeout.md"
+ - "docs/working-memory/provider-refactor/steps/todo-21-ccbuddy-builtin-config-ownership-and-model-activation-order.md"
+ - "docs/working-memory/provider-refactor/steps/todo-32-team-plan-settings-effective-provider-boundary.md"
+ - "docs/working-memory/provider-refactor/steps/todo-51-provider-access-consumer-and-offpeak-auth-closeout.md"
+ - "docs/working-memory/provider-refactor/steps/todo-55-provider-template-inheritance-cutover.md"
+ - "docs/model-provider-presets.md"
+ - "docs/model-provider-config-overrides.md"
+ - "docs/model-provider-catalog-metadata.md"
+ codeSeeds:
+ - file: "packages/provider/src/index.ts"
+ - symbol: "ProviderConfigResolver"
+ file: "packages/provider/src/resolver.ts"
+ - symbol: "ProviderTemplateMap"
+ file: "packages/provider/src/config/provider-config.ts"
+ - symbol: "AccountProviderService"
+ file: "packages/provider/src/account-provider-service.ts"
+ - symbol: "ApiProviderModelRuntime"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/provider-registry-model-runtime.ts"
+ - symbol: "RuntimeModelFactory"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/provider-registry-model-runtime.ts"
+ - symbol: "ApiProviderModelRuntime"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/provider-registry-model-runtime.ts"
+ invariants:
+ - "Model input/output format facts use the same properties.input_format/output_format shape in Config, Model, Runtime, and protocol; no parallel media capability DTO is authoritative."
+ - "Provider/Model Config overlays and Personal updates may be sparse; after resolve, validateComplete supplies diagnostics and Provider/Model requireComplete boundaries are the only path into Registry, whose configs and properties are statically complete."
+ - "Concrete Providers resolve from an optional Provider Template baseline followed by Built-in Concrete Provider, Account Config when applicable, and Personal Config; Personal remains the final user override."
+ - "Account Config is derived only for concrete Built-in Account Providers and does not inspect or constrain Personal-only Providers."
+ - "Provider Config has no top-level enabled field. A Personal Provider exists in Settings when its Personal definition exists; an Account Provider has fixed Family placement, while zhipu-account access.entitled expresses account eligibility. Registry admission still requires complete Effective Provider Config and an enabled, complete Model."
+ - "Every Account snapshot declares basedOnCCbuddyBuiltinRevision; Registry publishes only a matching Built-in/Account pair, retains the prior complete Registry while Account recomputes, and never blocks Personal-only updates on Account refresh."
+ - "CCbuddy Built-in Active/LKG and refresh control are physically isolated by normalized CCbuddy control-plane Endpoint under platform/appVersion; revisions and fallbacks never cross Endpoint directories."
+ - "Provider startup requires a valid trusted Bundled baseline, not every auxiliary source: disposable Active/LKG and Configured Default failures fall back, invalid Personal config is preserved for recovery then replaced by an empty in-memory or persisted overlay, and first-load Account resolution failure publishes a matching entitled=false overlay. Only an invalid Bundled baseline or an internal Resolver/Registry invariant failure blocks startup."
+ - "Personal Config cross-process observation uses one asynchronous content-revision polling loop rather than fs.watch; same-process saves publish immediately, other processes self-heal within one interval, and provider connectivity explicitly refreshes the Worker Registry before lookup without creating a Host-side Model path."
+ - "Normal Built-in and Account Built-in assembly maintains builtinModelIds while Personal settings maintains modelIds; strict source schemas reject fields outside each source boundary before all inputs enter the shared Provider/Model overlay types."
+ - "Model Config enabled is the only per-model execution gate. Built-in general rules provide the complete default, more specific Built-in rules may default-disable retired models, and later Personal provider-model rules express user overrides. Settings retains disabled members for editing and re-enabling, while Registry, selection candidates, defaults, fallbacks, exact ModelFactory creation, tests, and internal tasks reject them. Existing Active Models are not interrupted by a later config refresh."
+ - "Model Config has no visibility field. Provider visibility is the only user-facing visibility boundary; hidden complete Providers keep enabled models in the ordinary Registry for exact internal creation but are absent from Settings and Selection facades."
+ - "Personal Repository presence is the only authority for whether a normal Provider has been added: standard-builtin overlays are created only by addBuiltinProvider, standard-personal providers only by createPersonalProvider, and ordinary save updates existing overlays without reviving deletion; fixed Z.ai/BigModel Family providers may materialize their first sparse Personal overlay through Family settings."
+ - "At the inventory boundary, missing or null collections normalize to empty, Built-in membership wins a cross-source duplicate, and repeated ids within one array retain the first occurrence. Resolver ignores later duplicates deterministically and Settings normalizes Provider-owned arrays on save; no conflict DTO or suspended second identity is created."
+ - "Provider modelOrder and Personal top-level providerOrder own user sorting. The shared owned-order algorithm yields unmentioned Built-in + requested valid members + unmentioned Personal; Family Providers remain outside the sortable standard segment. Settings, Registry, and Selection preserve that inventory and filter it without secondary sorting."
+ - "Provider group is explicit at root declarations, immutable through Account/Personal overlays, and complete in Effective Provider, Registry, and Settings DTOs; it replaces an additional builtin boolean, and Renderer never infers grouping from ids, preset lists, or Personal map membership."
+ - "Effective Model Rules resolve against Effective Provider Config and are composed as CCbuddy Built-in rules + Personal Match rules + Personal provider-model rules; each group preserves internal order and Personal exact rules always apply last."
+ - "CCbuddy Built-in Model Rules use physical authoring order rather than a priority mechanism: complete baseline, Model Match, Model + API Match, Model + API + Endpoint Match, Provider-scoped Match for service facts that apply to all current/future members, then narrow Provider-Model Exact Rules; Resolver preserves that Built-in order without dynamic specificity sorting."
+ - "Z.ai/BigModel Start, Individual, and Team Plan Providers expose Image/Video input for every member because the plan service performs visual-to-text processing before the model API; this is a Provider-scoped Built-in Model Rule and never changes the native capability of ordinary Account API models. Individual and Team static membership is ordered GLM-5.3, GLM-5.3-Flash, GLM-5.2, GLM-5-Turbo, while Start membership remains Account-returned."
+ - "Both Built-in and Personal Model Rule sources may use structured provider-model rules for an exact providerId/modelId target or match rules for a target set. Built-in exact rules are release-owned; only Personal exact rules follow Settings rename/delete lifecycle. Match rules are never guessed, renamed, or deleted by Provider/Model lifecycle operations."
+ - "The Provider Config chain uses ccbuddyBuiltin/ccbuddy-builtin naming across domain APIs, source paths, process fields, environment variables, and packaged assets; the unshipped official naming has no alias, fallback, importer, or dual-read compatibility, while unrelated Official MCP/Plugin/CUA concepts retain their own terminology."
+ - "Account-type Provider Config does not manage account credentials; Services resolve them at request time and the server makes the final authorization decision."
+ - "Account Connection owns current-account facts. Built-in Provider Config owns zhipu-account family/mode; Account Built-in Provider Config projects only entitled and account-specific model membership, while account identity, Coding Plan project keys, Team scope, JWT, and other dynamic credentials stay inside request-time Services."
+ - "Start Plan, Individual Coding Plan, Team Coding Plan, and Off-Peak are separate fixed-kind Built-in Providers with enabled=true. Account Built-in overlay marks the compatible current connection entitled without rewriting Provider enabled, and Resolver admits only enabled, entitled, complete Providers to Registry."
+ - "Welcome manual API-key setup locates the fixed zai-api or bigmodel-api Built-in Provider and saves only a sparse Personal Provider Overlay. It does not create a custom Provider, copy Built-in endpoint/schema/models, enter Account Request Auth, or mutate an existing explicit Family Connection Selection."
+ - "Off-Peak request auth combines CCbuddy JWT, the compatible Coding Plan project key, and the Off-Peak ticket through one Registry-backed host-private credential resolver. Desktop Host dispatch does not scan Provider Settings or independently infer the current account Provider."
+ - "Five strict input schemas enforce CCbuddy Built-in Provider, Account Provider, Personal Provider, CCbuddy Built-in Model Rule, and Personal Model Rule field ownership before common overlay and completeness validation. UI visibility remains a separate presentation decision."
+ - "Provider Access owns challenge, requestSigning, and executionProtocol product protocols; Model Config and Runtime do not infer Captcha, V4 signing, or Off-Peak behavior from model/provider ids."
+ - "A request-auth Provider receives dynamic credentials per request and fails before network I/O when they are absent."
+ - "RuntimeModelFactoryInput cannot override Registry-owned context, properties, or provider options; execution-scoped MCS force is consumed where Provider messages are projected, including Full Compact, and never becomes Model Config or a ModelFactory capability input."
+ - "Published Legacy Provider store shapes may be parsed only by importer-private Services types and helpers; they are not Shared, Protocol, UI, Registry, Runtime, or test-fixture contracts."
+ - "The unversioned Legacy CLI importer migrates only explicitly represented facts without capability guesses; after the forced-upgrade compatibility window it and its legacy parser dependencies are removed, while normal schemaVersion migrations remain."
+ - "Core uses ModelSelection for future intent and Model for active execution; it does not call a model Adapter or query a physical Provider Connection."
+ - "The business Provider Registry is the only Provider/Model fact registry; Adapter execution projections and caches are one-way private implementation details."
+ - "Model identity does not carry reasoning options; do not introduce a general ModelIdentity while ModelSelection, Model, or local event fields already express the required semantics."
+ - "reasoningLevel.values is ordered from the lowest to the highest public level; default remains the ordinary request default, while explicitly low-cost auxiliary calls, including Provider connectivity, uniformly select values[0] without guessing disabled aliases."
+
+ - id: "capability.provider-endpoint-routing"
+ kind: "capability"
+ label: "CCbuddy CLI provider endpoint routing"
+ aliases:
+ - "provider endpoint routing"
+ - "proxy endpoint mapping"
+ - "agent configs proxy"
+ - "模型流量转发"
+ - "动态端点转发"
+ - "静态网关"
+ docs:
+ - "apps/ccbuddy-cli/docs/design/v2/model/provider-endpoint-routing.md"
+ - "apps/ccbuddy-cli/docs/design/v2/model/http-proxy.md"
+
+ - id: "capability.model-request-session-type-attribution"
+ kind: "capability"
+ label: "Model request session type attribution header"
+ aliases:
+ - "x-ccbuddy-session-type"
+ - "model request session type"
+ - "模型请求 session 分类"
+ - "主会话子代理请求归因"
+ docs:
+ - "docs/superpowers/specs/2026-06-09-agent-model-request-session-header-design.md"
+ - "apps/ccbuddy-cli/docs/design/v2/model/README.md"
+ codeSeeds:
+ - symbol: "ModelRequestSessionType"
+ file: "apps/ccbuddy-cli/packages/contracts/src/model/index.ts"
+ - symbol: "resolveModelRequestSessionTypeFromTaskType"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/model-request-session-type.ts"
+ - symbol: "createModelRequestAttributionHeaders"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-status.ts"
+
+ - id: "capability.anthropic-request-metadata"
+ kind: "capability"
+ label: "Anthropic request attribution metadata"
+ aliases:
+ - "Anthropic metadata"
+ - "metadata.user_id"
+ - "Anthropic device id"
+ - "Anthropic 请求归因"
+ - "模型请求设备 ID"
+ docs:
+ - "docs/anthropic-request-metadata.md"
+
+ - id: "capability.anthropic-reasoning-history-replay"
+ kind: "capability"
+ label: "Anthropic reasoning history replay and signature repair"
+ aliases:
+ - "thinking signature replay"
+ - "reasoning history normalization"
+ - "signature repair retry"
+ - "Anthropic 推理历史回放"
+ - "thinking 签名修复"
+ docs:
+ - "apps/ccbuddy-cli/docs/design/v2/model/anthropic-reasoning-history-replay.md"
+
+ - id: "capability.model-reasoning-request-projection"
+ kind: "capability"
+ label: "Provider-visible reasoning history projection"
+ aliases:
+ - "empty reasoning stub"
+ - "reasoning_content replay"
+ - "reasoning request projection"
+ - "空 reasoning 请求投影"
+ docs:
+ - "apps/ccbuddy-cli/docs/design/v2/model/README.md"
+ - "apps/ccbuddy-cli/docs/design/v2/model/anthropic-reasoning-history-replay.md"
+
+ - id: "capability.oauth-login-attribution"
+ kind: "capability"
+ label: "OAuth login marketing attribution"
+ aliases:
+ - "OAuth attribution"
+ - "marketing attribution"
+ - "OAuth 归因"
+ - "登录归因"
+ docs:
+ - "docs/oauth/app-marketing-attribution.md"
+
+ - id: capability.remote-delivery
+ kind: capability
+ label: Ordinary Web and SSH replayable delivery
+ aliases:
+ - Web direct server
+ - SSH replayable
+ - replayable
+ - 重连恢复
+ docs:
+ - docs/specs/mobile-remote-control-removal.md
+ codeSeeds:
+ - symbol: connectViaWebSocket
+ file: packages/client/src/websocket.ts
+ - symbol: createHttpServer
+ file: packages/server/src/http.ts
+ invariants:
+ - Desktop continuous and ordinary Web/SSH replayable delivery use the existing session owner and sequence; mobile pairing and relay are removed.
+
+ - id: "capability.workspace-identity"
+ kind: "capability"
+ label: "Workspace identity and remote runtime isolation"
+ aliases:
+ - "workspace identity"
+ - "remote workspace"
+ - "SSH"
+ - "WSL"
+ - "Docker"
+ - "工作区隔离"
+ docs:
+ - "docs/remote-workspace-session-unified-settings.md"
+ - "docs/ssh-remote-app-global-state-authority.md"
+ - "docs/v4-refactor/14-sessions-index.md"
+
+ - id: capability.remote-provider-provisioning
+ kind: capability
+ label: Remote Environment Provider Provisioning v0
+ aliases:
+ - provider provisioning
+ - remote provider sync
+ - Environment provider sync
+ - 远程 Provider 配置同步
+ - 远程环境模型配置镜像
+ docs:
+ - docs/remote/provider-provisioning.md
+ - docs/remote-workspace-session-unified-settings.md
+ - docs/working-memory/provider-refactor/steps/todo-56-remote-provider-provisioning-environment-sync.md
+ codeSeeds:
+ - symbol: ProviderProvisioningEnvironmentCoordinator
+ file: packages/desktop/src/main/providerProvisioningEnvironmentCoordinator.ts
+ - symbol: createProviderProvisioningSource
+ file: packages/services/src/model-provider/providerProvisioningSource.ts
+ - symbol: createProviderProvisioningTarget
+ file: packages/services/src/model-provider/providerProvisioningTarget.ts
+ invariants:
+ - The Local Environment is the v0 authority; each online Remote Environment receives a replace-style execution mirror, while Built-in Release and already-created Active Models are unchanged.
+ - Provisioning is deduplicated by Remote Environment rather than Workspace, window, workspacePath, workspaceIdentity, or remoteSessionId; one lane provides single-flight plus a latest-state trailing refresh.
+ - Desktop Main coordinates only Environment identity, trigger, generation, and correlation metadata. The selected Local Host reads Config and allowlisted credentials and sends the envelope to the Remote Target; Main never receives Config or Secret values.
+ - Desktop continuous and Web/SSH replayable session-delivery semantics are invariant under Provider Provisioning.
+
+ - id: "capability.remote-file-actions"
+ kind: "capability"
+ label: "Remote workspace file actions in local applications"
+ aliases:
+ - "WSL Explorer"
+ - "remote open in editor"
+ - "远程文件打开"
+ - "在资源管理器中打开"
+ docs:
+ - "docs/wsl-open-in-editor.md"
+
+ - id: "capability.workspace-file-tree"
+ kind: "capability"
+ label: "Workspace file tree navigation"
+ aliases:
+ - "file tree"
+ - "compact folders"
+ - "sticky folders"
+ - "文件树"
+ - "目录吸顶"
+ docs:
+ - "docs/ui/workspace-file-tree.md"
+ - "docs/testing/workspace-file-tree-e2e-coverage.md"
+
+ - id: "surface.workspace-file-tree-panel"
+ kind: "ui-surface"
+ label: "Workspace file tree panel"
+ codeSeeds:
+ - symbol: "WorkspaceFileTree"
+ file: "packages/ui/src/workspace-file-tree/WorkspaceFileTree.tsx"
+ - symbol: "WorkspaceFileTreeRowView"
+ file: "packages/ui/src/workspace-file-tree/WorkspaceFileTreeRowView.tsx"
+ - symbol: "WorkspaceFileTreeStickyFolders"
+ file: "packages/ui/src/workspace-file-tree/WorkspaceFileTreeStickyFolders.tsx"
+
+ - id: "state.workspace-file-tree-projection"
+ kind: "state-owner"
+ label: "Workspace file tree lazy-load and flattened row projection"
+ codeSeeds:
+ - symbol: "useWorkspaceFileTreeData"
+ file: "packages/ui/src/workspace-file-tree/useWorkspaceFileTreeData.ts"
+ - symbol: "flattenWorkspaceFileTreeRows"
+ file: "packages/ui/src/workspace-file-tree/model.ts"
+
+ - id: "evidence.workspace-file-tree-e2e"
+ kind: "evidence"
+ label: "Workspace file tree E2E coverage"
+ docs:
+ - "docs/testing/workspace-file-tree-e2e-coverage.md"
+
+ - id: "capability.prompt-attachment-materialization"
+ kind: "capability"
+ label: "Prompt attachment materialization"
+ aliases:
+ - "prompt attachment"
+ - "attachment staging"
+ - "WSL attachment"
+ - "附件物化"
+ - "附件预传"
+ - "图片派生路径"
+ - "媒体派生路径"
+ docs:
+ - "docs/remote-prompt-attachment-materialization.md"
+ - "docs/conversation-session-case-catalog.md"
+ - "apps/ccbuddy-cli/docs/design/v2/message-attachments.md"
+ - "docs/ui/chat-file-attachments.md"
+ codeSeeds:
+ - symbol: "NodeToolArtifactStore.ensureMediaAttachmentPath"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/index.ts"
+ - symbol: "projectMessagesWithMediaAttachmentPaths"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/helpers/media-attachment-path.ts"
+
+ - id: "capability.video-input"
+ kind: "capability"
+ label: "Video input"
+ aliases:
+ - "video attachment"
+ - "video Read"
+ - "视频输入"
+ - "视频附件"
+ docs:
+ - "docs/plans/2026-08-17-video-input-support-plan.md"
+ - "apps/ccbuddy-cli/docs/design/v2/message-attachments.md"
+ - "apps/ccbuddy-cli/docs/design/v2/tool/01-read.md"
+ codeSeeds:
+ - symbol: "ModelVideoContentBlock"
+ file: "apps/ccbuddy-cli/packages/contracts/src/model/index.ts"
+ - symbol: "resolveTurnAttachments"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/helpers/attachments.ts"
+ - symbol: "readVideoFile"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/handlers/read-video.ts"
+ - symbol: "toAiSdkMessages"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/transform.ts"
+
+ - id: "capability.task-persistence"
+ kind: "capability"
+ label: "Task persistence, index, and snapshots"
+ aliases:
+ - "task index"
+ - "snapshot"
+ - "restore"
+ - "任务持久化"
+ - "任务恢复"
+ docs:
+ - "docs/task/task-sqlite-index.md"
+ - "docs/task/task-snapshot-on-demand.md"
+ - "docs/working-memory/provider-refactor/steps/todo-109-database-migration-consolidation-and-rollback-readability.md"
+ - "docs/desktop/database-migration-startup-gate-plan.md"
+
+ - id: "capability.desktop-app-shutdown"
+ kind: "capability"
+ label: "Desktop app shutdown and update-install handoff"
+ aliases:
+ - "app quit"
+ - "Windows shutdown"
+ - "quitAndInstall"
+ - "应用退出"
+ - "更新安装退出"
+ docs:
+ - "docs/desktop/agent-shutdown-latency.md"
+ - "docs/desktop/windows-update-install-handoff.md"
+
+ - id: "capability.desktop-startup-critical-path"
+ kind: "capability"
+ label: "Desktop Local Host and active-workspace startup critical path"
+ aliases:
+ - "desktop startup performance"
+ - "runtime env prewarm"
+ - "active-first tab restore"
+ - "桌面启动性能"
+ - "环境预热"
+ - "活动工作区优先恢复"
+ - "数据库迁移启动 loading"
+ - "大库升级等待"
+ - "主界面前统一准备数据库"
+ - "迁移空间不足提示"
+ docs:
+ - "docs/desktop/startup-critical-path.md"
+ - "docs/desktop/database-migration-startup-gate-plan.md"
+ - "docs/desktop/database-startup-plan-review.md"
+ - "docs/working-memory/provider-refactor/steps/todo-145-global-database-startup-loading.md"
+ codeSeeds:
+ - symbol: "prepareRuntimeProcessEnvPatch"
+ file: "packages/services/src/runtime-tools/runtimeCommandEnv.ts"
+ - symbol: "createWindow"
+ file: "packages/desktop/src/main/desktopWindowLifecycle.ts"
+ - symbol: "useTabPersistence"
+ file: "packages/ui/src/hooks/useTabPersistence.ts"
+ - symbol: "RootStartupLoading"
+ file: "packages/ui/src/root/RootStartupLoading.tsx"
+ - symbol: "runSqliteSessionMigrations"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/session-store/migration-runner.ts"
+ - file: "packages/desktop/src/renderer/src/main.tsx"
+ - file: "packages/desktop/src/host/index.ts"
+ - symbol: "TaskIndexRepo"
+ file: "packages/services/src/session/taskIndexRepo.ts"
+ - symbol: "SqliteSessionMigrationError"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/session-store/errors.ts"
+ - symbol: "dispatchFinalArmsCustomEvent"
+ file: "packages/desktop/src/main/desktopArmsCustomEvent.ts"
+ invariants:
+ - "Todo145 planned: global database preparation precedes business Root and background admission; includes Host tasks-index initialization and session storage, preserves frozen migration SQL/checksums and SQLite authority. Current workspace-driven loading does not satisfy the planned contract."
+ - "Todo145 planned: structured storage-full errors drive UI and telemetry; periodic statfs aggregates observed available-space decline, never claims exclusive migration disk usage. Telemetry failure must not gate application readiness."
+
+ - id: "state.desktop-runtime-process-env"
+ kind: "state-owner"
+ label: "Per-window bounded runtime process environment patch"
+ codeSeeds:
+ - symbol: "buildRuntimeProcessEnvPatch"
+ file: "packages/services/src/runtime-tools/runtimeCommandEnv.ts"
+ - symbol: "initializeRuntimeProcessEnv"
+ file: "packages/services/src/runtime-tools/runtimeCommandEnv.ts"
+
+ - id: "state.desktop-tab-restore-projection"
+ kind: "state-owner"
+ label: "Renderer-local pending, active-ready, and complete tab restore projection"
+ codeSeeds:
+ - symbol: "useTabPersistence"
+ file: "packages/ui/src/hooks/useTabPersistence.ts"
+ - symbol: "completeTabRestore"
+ file: "packages/ui/src/store/tabStore.ts"
+
+ - id: "service.desktop-local-host-startup"
+ kind: "service"
+ label: "Desktop per-window Local Host startup gate"
+ codeSeeds:
+ - symbol: "createWindow"
+ file: "packages/desktop/src/main/desktopWindowLifecycle.ts"
+ - symbol: "spawnHostProcess"
+ file: "packages/desktop/src/main/desktopHostProcess.ts"
+
+ - id: "evidence.desktop-startup-critical-path-tests"
+ kind: "evidence"
+ label: "Desktop runtime env, Host deadline, and tab restore tests"
+ codeSeeds:
+ - file: "packages/services/test/runtimeCommandEnv.test.ts"
+ - file: "packages/desktop/test/desktopWindowLifecycle.test.ts"
+ - file: "packages/ui/test/useTabPersistence.test.ts"
+ - file: "packages/ui/test/remoteWorkspaceSessionPersistence.test.ts"
+
+ - id: "surface.app-usage-dashboard"
+ kind: "ui-surface"
+ label: "Settings App Usage dashboard"
+ codeSeeds:
+ - symbol: "AppUsagePanel"
+ file: "packages/ui/src/settings/usage-stats/AppUsagePanel.tsx"
+ - symbol: "UsageStatsSection"
+ file: "packages/ui/src/settings/UsageStatsSection.tsx"
+
+ - id: "surface.resource-manager-window"
+ kind: "ui-surface"
+ label: "Help menu Resource Manager window"
+ codeSeeds:
+ - symbol: "ResourceManagerApp"
+ file: "packages/ui/src/resource-manager/ResourceManagerApp.tsx"
+ - symbol: "StorageSection"
+ file: "packages/ui/src/resource-manager/storage/StorageSection.tsx"
+ - symbol: "NetworkSection"
+ file: "packages/ui/src/resource-manager/NetworkSection.tsx"
+ - symbol: "openResourceManager"
+ file: "packages/desktop/src/main/resourceManagerWindow.ts"
+ - symbol: "WindowsCaptionMenuButton"
+ file: "packages/ui/src/WindowsCaptionMenuButton.tsx"
+
+ - id: "surface.plan-usage"
+ kind: "ui-surface"
+ label: "Coding Plan and Team Plan usage surfaces"
+ codeSeeds:
+ - symbol: "CodingPlanUsagePanel"
+ file: "packages/ui/src/settings/usage-stats/CodingPlanUsagePanel.tsx"
+
+ - id: "surface.start-plan-manual-claim"
+ kind: "ui-surface"
+ label: "Marketing Touch claim banner and result dialog (formerly manual claim)"
+ docs:
+ - "docs/zai-start-plan-provider.md"
+ - "docs/cloud-content-dialog.md"
+ - "docs/testing/marketing-touch-e2e-implementation.md"
+ - "docs/marketing-touch-client.md"
+ codeSeeds:
+ - symbol: "MarketingBanner"
+ file: "packages/ui/src/components/marketing-touch/MarketingBanner.tsx"
+ - symbol: "MarketingDialogs"
+ file: "packages/ui/src/components/marketing-touch/MarketingDialogs.tsx"
+
+ - id: "capability.cloud-content-dialog"
+ kind: "capability"
+ label: "Cloud-driven content dialog and Hero resources"
+ aliases:
+ - "cloud content dialog"
+ - "remote Hero"
+ - "云端内容弹窗"
+ - "远端 Hero 资源"
+ docs:
+ - "docs/cloud-content-dialog.md"
+ codeSeeds:
+ - symbol: "CloudContentDialog"
+ file: "packages/ui/src/components/cloud-content-dialog/CloudContentDialog.tsx"
+ - symbol: "cloudContentPayloadSchema"
+ file: "packages/shared/src/cloudContent.ts"
+ - symbol: "CloudDialogHero"
+ file: "packages/ui/src/components/cloud-content-dialog/CloudDialogHero.tsx"
+
+ - id: "surface.cloud-content-preview"
+ kind: "ui-surface"
+ label: "Retired Desktop dev HTTP content preview; no production entry"
+ docs:
+ - "docs/cloud-content-dialog.md"
+ codeSeeds: []
+
+ - id: "service.cloud-content-assets"
+ kind: "state-owner"
+ label: "Host-exclusive content ZIP cache and resource leases"
+ codeSeeds:
+ - symbol: "createCloudContentService"
+ file: "packages/services/src/cloud-content/cloudContentService.ts"
+ - symbol: "createContentBundleCache"
+ file: "packages/services/src/cloud-content/contentBundleCache.ts"
+
+ - id: "shared.cloud-dialog-hero"
+ kind: "shared-ui"
+ label: "Sandboxed cloud dialog Hero renderer"
+ docs:
+ - "docs/cloud-content-dialog.md"
+ codeSeeds:
+ - symbol: "CloudDialogHero"
+ file: "packages/ui/src/components/cloud-content-dialog/CloudDialogHero.tsx"
+ - file: "packages/ui/src/assets/cloud-content/weekend-plan-hero.html"
+
+ - id: "state.start-plan-model-guide-request"
+ kind: "state-owner"
+ label: "Retired claim-triggered model guide; Marketing Touch does not select models"
+ codeSeeds: []
+
+ - id: "evidence.start-plan-manual-claim-tests"
+ kind: "evidence"
+ label: "Start Plan manual claim focused and Desktop E2E coverage"
+ docs:
+ - "docs/testing/marketing-touch-e2e-audit.md"
+ - "docs/testing/marketing-touch-e2e-implementation.md"
+ codeSeeds:
+ - file: "packages/ui/test/marketingTouchProvider.test.ts"
+ - file: "packages/desktop/test/e2e/ui-shell/manual-review/pending/marketing-touch-delivery.test.ts"
+ - file: "packages/desktop/test/e2e/ui-shell/manual-review/pending/marketing-touch-entitlement.test.ts"
+ - file: "packages/ui/test/aliyunCaptchaRuntime.test.ts"
+
+ - id: "surface.office-document-workbench"
+ kind: "ui-surface"
+ label: "Office preview and editing workbench"
+ codeSeeds:
+ - symbol: "PreviewPane"
+ file: "packages/ui/src/PreviewPane.tsx"
+ - symbol: "PdfViewer"
+ file: "packages/ui/src/components/ui/pdf-viewer.tsx"
+
+ - id: "surface.pptx-element-selection"
+ kind: "ui-surface"
+ label: "Preview Pane explicit PPTX element selection"
+ codeSeeds:
+ - symbol: "PptxPreviewViewer"
+ file: "packages/ui/src/components/ui/pptx-preview-viewer.tsx"
+ - symbol: "PptxRendererPreviewDocument"
+ file: "packages/ui/src/presentation/pptxRendererPreviewEngine.ts"
+
+ - id: "surface.composer-presentation-reference"
+ kind: "ui-surface"
+ label: "Conversation Composer PPTX element reference chip"
+ codeSeeds:
+ - symbol: "ConversationComposer"
+ file: "packages/ui/src/v4/ConversationComposer.tsx"
+ - symbol: "PptxElementReferenceChip"
+ file: "packages/ui/src/v4/composer/PptxElementReferenceChip.tsx"
+ - symbol: "serializeComposerPromptContexts"
+ file: "packages/ui/src/v4/composer/composerPromptContexts.ts"
+
+ - id: "surface.computer-use-task-experience"
+ kind: "ui-surface"
+ label: "Computer Use task, permission, and takeover experience"
+ codeSeeds:
+ - symbol: "ComputerUseSection"
+ file: "packages/ui/src/settings/ComputerUseSection.tsx"
+ - symbol: "useCuaPermissionStatus"
+ file: "packages/ui/src/hooks/useCuaPermissionStatus.ts"
+ - symbol: "CuaToolCallBlock"
+ file: "packages/ui/src/ToolCallBlocks/renderers/cua.tsx"
+ - symbol: "createWindowsCuaOperationIndicator"
+ file: "packages/desktop/src/main/windowsCuaOperationIndicator.ts"
+ - symbol: "createCuaOperationTurnTracker"
+ file: "packages/services/src/ccbuddy-agent/cuaOperationTurnTracker.ts"
+
+ - id: "surface.browser-use-side-pane-guest"
+ kind: "ui-surface"
+ label: "Browser Use side pane guest and background capture layer"
+ codeSeeds:
+ - symbol: "BrowserUseSidePaneContent"
+ file: "packages/ui/src/browser-use/BrowserUseSidePaneContent.tsx"
+ - symbol: "UnifiedBrowserView"
+ file: "packages/ui/src/browser-use/UnifiedBrowserView.tsx"
+
+ - id: "surface.human-browser-side-pane-guest"
+ kind: "ui-surface"
+ label: "Human-opened Browser side pane guest"
+ codeSeeds:
+ - symbol: "AnimatedSidePanePanel"
+ file: "packages/ui/src/app-shell/AnimatedSidePanePanel.tsx"
+ - symbol: "UnifiedBrowserView"
+ file: "packages/ui/src/browser-use/UnifiedBrowserView.tsx"
+ - symbol: "useAppPanels"
+ file: "packages/ui/src/app-shell/useAppPanels.ts"
+
+ - id: "surface.desktop-network-policy-setting"
+ kind: "ui-surface"
+ label: "Desktop HTTP proxy, No Proxy, and custom certificate settings"
+ codeSeeds:
+ - symbol: "GeneralSectionContent"
+ file: "packages/ui/src/settingsPageHelpers.tsx"
+ - symbol: "SettingsPage"
+ file: "packages/ui/src/SettingsPage.tsx"
+
+ - id: "surface.browser-tab-shell"
+ kind: "ui-surface"
+ label: "Desktop Browser and Browser Use logical tab shell"
+ codeSeeds:
+ - symbol: "AnimatedSidePanePanel"
+ file: "packages/ui/src/app-shell/AnimatedSidePanePanel.tsx"
+ - symbol: "BrowserUseSidePaneContent"
+ file: "packages/ui/src/browser-use/BrowserUseSidePaneContent.tsx"
+ - symbol: "UnifiedBrowserView"
+ file: "packages/ui/src/browser-use/UnifiedBrowserView.tsx"
+
+ - id: "surface.project-task-list"
+ kind: "ui-surface"
+ label: "Project workspace task list"
+ aliases:
+ - "Project sidebar"
+ - "workspace task rows"
+ - "项目任务列表"
+ codeSeeds:
+ - symbol: "WorkspaceSidebar"
+ file: "packages/ui/src/WorkspaceSidebar.tsx"
+ - symbol: "SortableWorkspaceSidebarItem"
+ file: "packages/ui/src/SortableWorkspaceSidebar.tsx"
+ - symbol: "WorkspaceSidebarItem"
+ file: "packages/ui/src/WorkspaceSidebarItem.tsx"
+
+ - id: "capability.task-title-overflow-marquee"
+ kind: "capability"
+ label: "Task title overflow fade and row-hover marquee"
+ aliases:
+ - "task title marquee"
+ - "task name mask"
+ - "task 名称走马灯"
+ - "task 名称 mask"
+ docs:
+ - "docs/ui/task-item-title-overflow.md"
+ codeSeeds:
+ - symbol: "TaskTitleOverflowText"
+ file: "packages/ui/src/components/TaskTitleOverflowText.tsx"
+
+ - id: "shared-ui.task-title-overflow-text"
+ kind: "shared-ui"
+ label: "Shared task title overflow and marquee renderer"
+ codeSeeds:
+ - symbol: "TaskTitleOverflowText"
+ file: "packages/ui/src/components/TaskTitleOverflowText.tsx"
+
+ - id: "surface.preview-pane-office-file"
+ kind: "ui-surface"
+ label: "Preview Pane Office file surface"
+ codeSeeds:
+ - symbol: "PreviewPane"
+ file: "packages/ui/src/PreviewPane.tsx"
+ - symbol: "PreviewPaneOfficeContent"
+ file: "packages/ui/src/previewPaneOfficeContent.tsx"
+
+ - id: "surface.conversation-assistant-file-preview"
+ kind: "ui-surface"
+ label: "Conversation assistant file preview cards"
+ codeSeeds:
+ - symbol: "AssistantPreviewCards"
+ file: "packages/ui/src/AssistantPreviewCards.tsx"
+ - symbol: "AssistantTextRowView"
+ file: "packages/ui/src/v4/ConversationRowView.tsx"
+
+ - id: "surface.git-review-file-actions"
+ kind: "ui-surface"
+ label: "Git review file context actions"
+ aliases:
+ - "Review file manager action"
+ - "审查区文件管理器"
+ codeSeeds:
+ - symbol: "GitPane"
+ file: "packages/ui/src/GitPane.tsx"
+ - symbol: "GitPaneChangeCard"
+ file: "packages/ui/src/GitPaneChangeCard.tsx"
+
+ - id: "shared-ui.remote-file-context-actions"
+ kind: "shared-ui"
+ label: "Workspace-scoped remote file context actions"
+ codeSeeds:
+ - symbol: "useFileContextActions"
+ file: "packages/ui/src/hooks/useFileContextActions.ts"
+ - symbol: "useTaskListItemContextActions"
+ file: "packages/ui/src/useTaskListItemContextActions.ts"
+ - symbol: "useWorkspaceOpenInEditorTarget"
+ file: "packages/ui/src/hooks/useWorkspaceOpenInEditorTarget.ts"
+
+ - id: "surface.grouped-task-list"
+ kind: "ui-surface"
+ label: "Custom grouped task list"
+ aliases:
+ - "Group sidebar"
+ - "grouped tasks"
+ - "分组任务列表"
+ codeSeeds:
+ - symbol: "WorkspaceGroupedTasksSection"
+ file: "packages/ui/src/WorkspaceGroupedTasksSection.tsx"
+ - symbol: "useGroupedTaskView"
+ file: "packages/ui/src/hooks/useGroupedTaskView.ts"
+
+ - id: "surface.pinned-task-list"
+ kind: "ui-surface"
+ label: "Global pinned task list"
+ aliases:
+ - "Pinned sidebar"
+ - "已置顶"
+ - "置顶任务列表"
+ codeSeeds:
+ - symbol: "WorkspacePinnedTasksSection"
+ file: "packages/ui/src/WorkspacePinnedTasksSection.tsx"
+
+ - id: "state.task-list-row-projection"
+ kind: "state-owner"
+ label: "Task list row and membership projection"
+ codeSeeds:
+ - symbol: "useWorkspaceTaskLists"
+ file: "packages/ui/src/hooks/useWorkspaceTaskLists.ts"
+ - symbol: "buildTaskListResult"
+ file: "packages/ui/src/v4/buildTaskListResultFromSessions.ts"
+ - symbol: "buildGroupedTaskViewFromSessions"
+ file: "packages/ui/src/lib/buildGroupedTaskViewFromSessions.ts"
+
+ - id: "state.preview-pane-office-render"
+ kind: "state-owner"
+ label: "Preview Pane local Office render lifecycle"
+ codeSeeds:
+ - symbol: "PreviewPaneOfficeDocxContent"
+ file: "packages/ui/src/previewPaneOfficeDocxContent.tsx"
+
+ - id: "state.pptx-preview-element-selection"
+ kind: "state-owner"
+ label: "PPTX viewer generation-scoped element selection"
+ codeSeeds:
+ - symbol: "PptxPreviewViewer"
+ file: "packages/ui/src/components/ui/pptx-preview-viewer.tsx"
+
+ - id: "state.pptx-preview-ai-edit-draft"
+ kind: "state-owner"
+ label: "PPTX viewer-local AI edit comment draft"
+ codeSeeds:
+ - symbol: "PptxSelectionActionBar"
+ file: "packages/ui/src/components/ui/pptx-selection-action-bar.tsx"
+ - symbol: "PptxPreviewViewer"
+ file: "packages/ui/src/components/ui/pptx-preview-viewer.tsx"
+
+ - id: "state.composer-presentation-reference-draft"
+ kind: "state-owner"
+ label: "Renderer-local scoped Composer PPTX references"
+ codeSeeds:
+ - symbol: "ConversationComposer"
+ file: "packages/ui/src/v4/ConversationComposer.tsx"
+
+ - id: "state.pptx-reference-preview-navigation"
+ kind: "state-owner"
+ label: "Renderer-local PPTX reference file and page navigation request"
+ codeSeeds:
+ - symbol: "PptxPreviewViewer"
+ file: "packages/ui/src/components/ui/pptx-preview-viewer.tsx"
+ - symbol: "openCodeViewerSidePane"
+ file: "packages/ui/src/lib/workspaceSidePane.ts"
+
+ - id: "service.ccbuddy-task-list"
+ kind: "service"
+ label: "CCbuddy task list service"
+ codeSeeds:
+ - symbol: "ICCbuddyTaskService"
+ file: "packages/services/src/session/ccbuddyTaskService.ts"
+
+ - id: "service.workspace-file-binary-preview"
+ kind: "service"
+ label: "Workspace-scoped bounded binary preview read"
+ codeSeeds:
+ - symbol: "readBinaryPreview"
+ file: "packages/services/src/file/fileService.ts"
+
+ - id: "service.ccbuddy-task-index-syncer"
+ kind: "service"
+ label: "Sessions-index to task-index synchronizer"
+ codeSeeds:
+ - symbol: "createCCbuddyTaskIndexSyncer"
+ file: "packages/services/src/ccbuddy-agent/ccbuddyTaskIndexSyncer.ts"
+
+ - id: "surface.conversation-model-control"
+ kind: "ui-surface"
+ label: "Conversation composer model control"
+ codeSeeds:
+ - symbol: "V4ComposerModelControlsImpl"
+ file: "packages/ui/src/v4/composer/V4ComposerToolbar.tsx"
+
+ - id: "surface.subagent-session-detail"
+ kind: "ui-surface"
+ label: "Read-only Subagent session conversation detail"
+ aliases:
+ - "Subagent detail tab"
+ - "child session detail"
+ - "子智能体详情"
+ docs:
+ - "docs/subagent-session-side-tabs.md"
+ - "docs/v4-composer-api-retry-status.md"
+ - "docs/chat/manual-model-switch-resolution-chain.md"
+ codeSeeds:
+ - symbol: "SubagentSessionSidePane"
+ file: "packages/ui/src/app-shell/SubagentSessionSidePane.tsx"
+ - symbol: "SessionPane"
+ file: "packages/ui/src/v4/SessionPane.tsx"
+ - symbol: "ConversationTimeline"
+ file: "packages/ui/src/v4/ConversationTimeline.tsx"
+ - symbol: "ConversationTurnGroup"
+ file: "packages/ui/src/v4/ConversationTurnGroup.tsx"
+
+ - id: "surface.conversation-query-edit"
+ kind: "ui-surface"
+ label: "Conversation real-user query inline edit"
+ aliases:
+ - "edit user query"
+ - "编辑用户消息"
+ - "队列消费消息编辑"
+ codeSeeds:
+ - symbol: "ConversationRowView"
+ file: "packages/ui/src/v4/ConversationRowView.tsx"
+ - symbol: "SessionPane"
+ file: "packages/ui/src/v4/SessionPane.tsx"
+
+ - id: "surface.parent-task-blocking-interaction"
+ kind: "ui-surface"
+ label: "Parent task permission and user-input dialogs"
+ aliases:
+ - "subagent permission"
+ - "subagent AskUserQuestion"
+ - "子代理权限"
+ - "子代理提问"
+ codeSeeds:
+ - symbol: "V4InteractionDialogs"
+ file: "packages/ui/src/v4/V4InteractionDialogs.tsx"
+ - symbol: "onPermissionRequested"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/product-projection.ts"
+
+ - id: "surface.conversation-tool-call"
+ kind: "ui-surface"
+ label: "Conversation tool call cards"
+ aliases:
+ - "TaskOutput card"
+ - "RespondToCoordinator card"
+ - "工具调用卡片"
+ - "CUA Group"
+ - "Computer Use 工具聚合"
+ docs:
+ - "docs/ui/tool-display-rendering.md"
+ - "docs/task-output-contract.md"
+ - "docs/subagent-respond-to-coordinator.md"
+ - "docs/cua/cua-tool-app-identity-summary.md"
+ codeSeeds:
+ - symbol: "resolveToolCallRenderer"
+ file: "packages/ui/src/ToolCallBlocks.tsx"
+ - symbol: "toolCallRowToLegacyNode"
+ file: "packages/ui/src/v4/toolCallRowAdapter.ts"
+ - symbol: "ProductProjection"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/product-projection.ts"
+ - symbol: "prepareCuaGroupFlowItems"
+ file: "packages/ui/src/v4/conversationCuaGroups.ts"
+
+ - id: "surface.ask-user-question-auto-resolution-setting"
+ kind: "ui-surface"
+ label: "Settings interaction auto-resolution switch"
+ codeSeeds:
+ - symbol: "GeneralSectionContent"
+ file: "packages/ui/src/settingsPageHelpers.tsx"
+ - symbol: "SettingsPage"
+ file: "packages/ui/src/SettingsPage.tsx"
+
+ - id: "surface.message-stream-reasoning-setting"
+ kind: "ui-surface"
+ label: "Settings message stream reasoning visibility switch"
+ codeSeeds:
+ - symbol: "GeneralSectionContent"
+ file: "packages/ui/src/settingsPageHelpers.tsx"
+ - symbol: "SettingsPage"
+ file: "packages/ui/src/SettingsPage.tsx"
+
+ - id: "state.conversation-reasoning-visibility"
+ kind: "state-owner"
+ label: "Renderer-resolved conversation reasoning visibility"
+ codeSeeds:
+ - symbol: "SessionPane"
+ file: "packages/ui/src/v4/SessionPane.tsx"
+ - symbol: "isConversationReasoningRowVisible"
+ file: "packages/ui/src/v4/conversationRowContext.ts"
+
+ - id: "surface.data-directory-setting"
+ kind: "ui-surface"
+ label: "Settings data storage directory"
+ codeSeeds:
+ - symbol: "GeneralSectionContent"
+ file: "packages/ui/src/settingsPageHelpers.tsx"
+ - symbol: "SettingsPage"
+ file: "packages/ui/src/SettingsPage.tsx"
+
+ - id: "surface.model-io-full-retention-setting"
+ kind: "ui-surface"
+ label: "Settings ModelIO full retention switch"
+ codeSeeds:
+ - symbol: "GeneralSectionContent"
+ file: "packages/ui/src/settingsPageHelpers.tsx"
+ - symbol: "SettingsPage"
+ file: "packages/ui/src/SettingsPage.tsx"
+
+ - id: "surface.conversation-query-navigator"
+ kind: "ui-surface"
+ label: "Conversation complete query navigator rail"
+ codeSeeds:
+ - symbol: "ConversationTurnNavigator"
+ file: "packages/ui/src/v4/ConversationTurnNavigator.tsx"
+ - symbol: "ConversationTimeline"
+ file: "packages/ui/src/v4/ConversationTimeline.tsx"
+
+ - id: "surface.conversation-composer-attachments"
+ kind: "ui-surface"
+ label: "Conversation composer attachments"
+ codeSeeds:
+ - symbol: "useComposerAttachments"
+ file: "packages/ui/src/v4/composer/useComposerAttachments.ts"
+
+ - id: "surface.conversation-sent-media-preview"
+ kind: "ui-surface"
+ label: "Sent conversation image and video preview"
+ aliases:
+ - "sent video preview"
+ - "已发送视频预览"
+ - "媒体附件预览"
+ docs:
+ - "docs/plans/2026-08-17-video-input-support-plan.md"
+ - "docs/conversation-session-case-catalog.md"
+ codeSeeds:
+ - symbol: "UserInputAttachmentList"
+ file: "packages/ui/src/v4/ConversationRowView.tsx"
+ - symbol: "ChatMediaAttachmentPreviewDialog"
+ file: "packages/ui/src/ChatMediaAttachmentPreviewDialog.tsx"
+ - symbol: "ConversationV4Gateway.attachmentRead"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/v4-gateway.ts"
+ - symbol: "ConversationV4Gateway.attachmentPreviewSource"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/v4-gateway.ts"
+ - symbol: "installLocalMediaPreviewProtocol"
+ file: "packages/desktop/src/main/localMediaPreviewProtocol.ts"
+
+ - id: "surface.automation-edit"
+ kind: "ui-surface"
+ label: "Automation edit model control"
+ codeSeeds:
+ - symbol: "AutomationEditView"
+ file: "packages/ui/src/settings/AutomationEditView.tsx"
+
+ - id: "surface.automation-template-gallery"
+ kind: "ui-surface"
+ label: "Scheduled and Off-Peak template gallery"
+ aliases:
+ - "scheduled-task templates"
+ - "off-peak-task templates"
+ - "自动化模板"
+ codeSeeds:
+ - symbol: "AutomationsSection"
+ file: "packages/ui/src/settings/AutomationsSection.tsx"
+ - symbol: "AutomationTemplateSkeletonGrid"
+ file: "packages/ui/src/settings/AutomationTemplateSkeletonGrid.tsx"
+
+ - id: "surface.off-peak-home-template-carousel"
+ kind: "ui-surface"
+ label: "New Task Off-Peak template carousel"
+ codeSeeds:
+ - symbol: "OffPeakNewTaskEntry"
+ file: "packages/ui/src/v4/OffPeakNewTaskEntry.tsx"
+
+ - id: "surface.off-peak-creation"
+ kind: "ui-surface"
+ label: "Off-Peak creation entries and result reporting"
+ codeSeeds:
+ - symbol: "OffPeakNewTaskEntry"
+ file: "packages/ui/src/v4/OffPeakNewTaskEntry.tsx"
+ - symbol: "handleOffPeakSubmit"
+ file: "packages/ui/src/settings/AutomationsSection.tsx"
+
+ - id: "surface.off-peak-upgrade"
+ kind: "ui-surface"
+ label: "Off-Peak Coding Plan upgrade entries"
+ codeSeeds:
+ - symbol: "OffPeakNewTaskEntry"
+ file: "packages/ui/src/v4/OffPeakNewTaskEntry.tsx"
+ - symbol: "handleOpenCodingPlanUpgrade"
+ file: "packages/ui/src/settings/AutomationsSection.tsx"
+
+ - id: "surface.conversation-automation-tools"
+ kind: "ui-surface"
+ label: "Conversation automation management tools"
+ aliases:
+ - "CronCreate"
+ - "CronList"
+ - "CronUpdate"
+ - "CronDelete"
+ - "会话内定时任务管理"
+ codeSeeds:
+ - symbol: "cronUpdateToolEntry"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/handlers/cron.ts"
+ - symbol: "createProtocolAutomationPort"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/automation-port.ts"
+ - symbol: "resolveCronAutomationTurnCards"
+ file: "packages/ui/src/v4/ConversationTurnGroup.tsx"
+
+ - id: "surface.user-subagent-form"
+ kind: "ui-surface"
+ label: "User Subagent form model control"
+ codeSeeds:
+ - symbol: "SubagentForm"
+ file: "packages/ui/src/settings/SubagentsSection.tsx"
+
+ - id: "surface.builtin-subagent-model"
+ kind: "ui-surface"
+ label: "Built-in and plugin Subagent model override"
+ codeSeeds:
+ - symbol: "SubagentModelOverrideControl"
+ file: "packages/ui/src/settings/SubagentsSection.tsx"
+
+ - id: "surface.provider-settings"
+ kind: "ui-surface"
+ label: "Model provider settings and model capability editor"
+ docs:
+ - "docs/working-memory/provider-refactor/steps/todo-98-builtin-option-map-formatting-regression.md"
+ - "docs/working-memory/provider-refactor/steps/todo-93-account-plan-loss-manual-switch.md"
+ - "docs/working-memory/provider-refactor/steps/todo-92-model-smart-config-and-override-feedback.md"
+ - "docs/working-memory/provider-refactor/steps/todo-91-connectivity-probe-prompt-contract.md"
+ - "docs/working-memory/provider-refactor/steps/todo-90-model-add-edit-shared-recommended-draft.md"
+ - "docs/superpowers/specs/2026-06-04-model-provider-model-editor-design.md"
+ - "docs/superpowers/specs/2026-08-21-model-provider-modality-settings-design.md"
+ - "docs/working-memory/provider-refactor/design/registry/model-membership-and-enablement.md"
+ - "docs/working-memory/provider-refactor/design/registry/provider-and-model-configuration-overview.md"
+ - "docs/working-memory/provider-refactor/design/registry/settings.md"
+ - "docs/working-memory/provider-refactor/steps/todo-21-ccbuddy-builtin-config-ownership-and-model-activation-order.md"
+ - "docs/working-memory/provider-refactor/steps/todo-61-provider-template-picker-and-brand-metadata.md"
+ codeSeeds:
+ - symbol: "ModelProviderSection"
+ file: "packages/ui/src/settings/ModelProviderSection.tsx"
+ - symbol: "createAccountConnectionRefreshObserver"
+ file: "packages/ui/src/root/accountConnectionRefreshObserver.ts"
+ - symbol: "refreshRestoredOAuthProviderFamilyAfterStartup"
+ file: "packages/ui/src/root/oauthProviderFamilySelectionRefresh.ts"
+ - symbol: "useRootOAuthEffects"
+ file: "packages/ui/src/root/useRootOAuthEffects.ts"
+ - symbol: "ProviderModelMetadataDialog"
+ file: "packages/ui/src/settings/model-provider-section/ProviderModelMetadataDialog.tsx"
+ - symbol: "ProviderModelInputModalityOptions"
+ file: "packages/ui/src/settings/model-provider-section/ProviderModelModalityOptions.tsx"
+ - symbol: "ModelRowInput"
+ file: "packages/ui/src/settings/model-provider-section/ProviderFormControls.tsx"
+ - symbol: "InlineEditableProviderCard"
+ file: "packages/ui/src/settings/model-provider-section/InlineEditableProviderCard.tsx"
+ - symbol: "ModelProviderSectionDetail"
+ file: "packages/ui/src/settings/model-provider-section/Detail.tsx"
+ - symbol: "ProviderTemplatePicker"
+ file: "packages/ui/src/settings/model-provider-section/ProviderTemplatePicker.tsx"
+ - symbol: "ProviderLogo"
+ file: "packages/ui/src/settings/model-provider-section/ProviderLogo.tsx"
+ - symbol: "ProviderDetailFeedbackBoundary"
+ file: "packages/ui/src/settings/model-provider-section/ProviderDetailFeedback.tsx"
+ - symbol: "resolveProviderModelDraftCommit"
+ file: "packages/ui/src/settings/model-provider-section/ProviderModelMetadata.ts"
+
+ - id: "capability.customize-capability-search"
+ kind: "capability"
+ label: "Scope-aware Plugin capability search"
+ aliases:
+ - "Plugin search"
+ - "Customize search"
+ - "插件 MCP Skills 搜索"
+ docs:
+ - "docs/plugin-capabilities-settings.md"
+ codeSeeds:
+ - symbol: "PluginsSection"
+ file: "packages/ui/src/settings/PluginsSection.tsx"
+
+ - id: "surface.customize-capability-settings"
+ kind: "ui-surface"
+ label: "Plugin, MCPs and Skills settings"
+ codeSeeds:
+ - symbol: "PluginsSection"
+ file: "packages/ui/src/settings/PluginsSection.tsx"
+ - symbol: "McpSettingsSection"
+ file: "packages/ui/src/settings/McpSettingsSection.tsx"
+ - symbol: "SkillsSection"
+ file: "packages/ui/src/settings/SkillsSection.tsx"
+
+ - id: "state.customize-tab-search-query"
+ kind: "state-owner"
+ label: "Plugin tab-local search query state"
+ codeSeeds:
+ - symbol: "PluginsSection"
+ file: "packages/ui/src/settings/PluginsSection.tsx"
+
+ - id: "evidence.customize-capability-settings-tests"
+ kind: "evidence"
+ label: "Plugin scope projection and search tests"
+ docs:
+ - "docs/plugin-capabilities-settings.md"
+ codeSeeds:
+ - file: "packages/ui/test/pluginSettingsSection.test.ts"
+ - file: "packages/ui/test/pluginCapabilityProjection.test.ts"
+
+ - id: "capability.skill-catalog-discovery"
+ kind: "capability"
+ label: "Workspace and user Skill catalog discovery"
+ aliases:
+ - "Skills discovery"
+ - ".ccbuddy/skills and .agents/skills"
+ - "技能目录发现"
+ - "用户技能引用"
+ docs:
+ - "docs/external-agent-skills-import.md"
+ - "docs/ui/mention-panel-layout.md"
+ codeSeeds:
+ - symbol: "resolveDefaultSkillRoots"
+ file: "apps/ccbuddy-cli/packages/adapters/src/skills/roots.ts"
+ - symbol: "createSkillsService"
+ file: "packages/services/src/skills/skillsService.ts"
+ - symbol: "filterSkillsForProvider"
+ file: "packages/ui/src/lib/skillSourceFilter.ts"
+
+ - id: "surface.composer-skill-compatibility-picker"
+ kind: "ui-surface"
+ label: "Composer compatible Skill picker ($ and Yen aliases)"
+ aliases:
+ - "$ Skills picker"
+ - "$ 面板"
+ - "¥ 技能面板"
+ codeSeeds:
+ - symbol: "MentionPlugin"
+ file: "packages/ui/src/mentions/MentionPlugin.tsx"
+ - symbol: "useSkillsMentionProvider"
+ file: "packages/ui/src/mentions/providers/skillsMentionProvider.ts"
+
+ - id: "service.workspace-skills-catalog"
+ kind: "service"
+ label: "Host workspace and user Skills catalog service"
+ codeSeeds:
+ - symbol: "createSkillsService"
+ file: "packages/services/src/skills/skillsService.ts"
+ - symbol: "discoverSkills"
+ file: "packages/services/src/skills/skillsService.ts"
+
+ - id: "persistence.skill-roots"
+ kind: "persistence"
+ label: "Workspace and user .ccbuddy/skills and .agents/skills roots"
+ docs:
+ - "docs/external-agent-skills-import.md"
+ codeSeeds:
+ - symbol: "resolveDefaultSkillRoots"
+ file: "apps/ccbuddy-cli/packages/adapters/src/skills/roots.ts"
+ - symbol: "resolveAncestorWorkspaceRoots"
+ file: "packages/services/src/skills/skillsService.ts"
+
+ - id: "evidence.skill-catalog-discovery-tests"
+ kind: "evidence"
+ label: "Skill root discovery and composer source filtering tests"
+ docs:
+ - "docs/external-agent-skills-import.md"
+ codeSeeds:
+ - file: "apps/ccbuddy-cli/packages/adapters/tests/skills.test.ts"
+ - file: "packages/services/test/skillsService.test.ts"
+ - file: "packages/ui/test/skillSourceFilter.test.ts"
+
+ - id: "capability.command-workspace-scope"
+ kind: "capability"
+ label: "Concrete Workspace scope selection for Commands"
+ aliases:
+ - "Command Workspace scope"
+ - "Command new Workspace selector"
+ - "命令工作区选择"
+ docs:
+ - "docs/settings-command-list.md"
+ codeSeeds:
+ - symbol: "CommandsSection"
+ file: "packages/ui/src/settings/CommandsSection.tsx"
+ - symbol: "CommandForm"
+ file: "packages/ui/src/settings/CommandForm.tsx"
+
+ - id: "surface.command-settings-editor"
+ kind: "ui-surface"
+ label: "Command settings create and edit form"
+ codeSeeds:
+ - symbol: "CommandsSection"
+ file: "packages/ui/src/settings/CommandsSection.tsx"
+ - symbol: "CommandForm"
+ file: "packages/ui/src/settings/CommandForm.tsx"
+
+ - id: "state.command-form-scope-key"
+ kind: "state-owner"
+ label: "Command form concrete User or Workspace scope key"
+ codeSeeds:
+ - symbol: "CommandsSection"
+ file: "packages/ui/src/settings/CommandsSection.tsx"
+
+ - id: "shared-ui.customize-scope-menu"
+ kind: "shared-ui"
+ label: "User and concrete Workspace scope menu"
+ codeSeeds:
+ - symbol: "PluginScopeMenu"
+ file: "packages/ui/src/settings/PluginScopeMenu.tsx"
+
+ - id: "service.workspace-commands"
+ kind: "service"
+ label: "Workspace-resolved Commands Host service"
+ codeSeeds:
+ - symbol: "useWorkspaceServicesResolution"
+ file: "packages/ui/src/hooks/useWorkspaceServices.tsx"
+ - symbol: "createCommandsService"
+ file: "packages/services/src/commands/commandsService.ts"
+
+ - id: "persistence.command-settings-directories"
+ kind: "persistence"
+ label: "User and Workspace command directories"
+ docs:
+ - "docs/settings-command-list.md"
+ codeSeeds:
+ - symbol: "getCommandsRootForStorage"
+ file: "packages/services/src/commands/commandsService.ts"
+
+ - id: "evidence.command-workspace-scope-tests"
+ kind: "evidence"
+ label: "Command Workspace scope selection tests"
+ docs:
+ - "docs/settings-command-list.md"
+ codeSeeds:
+ - file: "packages/ui/test/settingsCommandWorkspaceScope.test.ts"
+
+ - id: "surface.project-memory-settings"
+ kind: "ui-surface"
+ label: "Project Memory toggle and local-only project-first read-only viewer"
+ codeSeeds:
+ - symbol: "MemorySettingsSection"
+ file: "packages/ui/src/settings/MemorySettingsSection.tsx"
+ - symbol: "MemorySettingsViewer"
+ file: "packages/ui/src/settings/MemorySettingsViewer.tsx"
+
+ - id: "shared-ui.model-config-select"
+ kind: "shared-ui"
+ label: "ModelConfigSelect"
+ codeSeeds:
+ - symbol: "ModelConfigSelect"
+ file: "packages/ui/src/ModelConfigSelect.tsx"
+
+ - id: "shared-ui.model-select-groups"
+ kind: "shared-ui"
+ label: "Model selection option builder"
+ codeSeeds:
+ - symbol: "buildRegistryModelSelectGroups"
+ file: "packages/ui/src/lib/modelSelectionGroups.ts"
+
+ - id: "shared-ui.model-providers-hook"
+ kind: "shared-ui"
+ label: "Model provider registry hook"
+ codeSeeds:
+ - symbol: "useModelProviders"
+ file: "packages/ui/src/hooks/useModelProviders.ts"
+
+ - id: "state.conversation-projection"
+ kind: "state-owner"
+ label: "Conversation UI projection and current runtime config"
+ docs:
+ - "docs/conversation-protocol-declaration.md"
+ - "docs/v4-refactor/03-commands.md"
+ - "docs/v4-refactor/04-sync-and-recovery.md"
+ codeSeeds:
+ - symbol: "ConversationProjectionStore"
+ file: "packages/ui/src/v4/conversationProjectionStore.ts"
+ - symbol: "expectAcceptedInputProjection"
+ file: "packages/ui/src/v4/conversationProjectionStore.ts"
+ - symbol: "ProductProjection"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/product-projection.ts"
+ invariants:
+ - "An accepted sendText waits for the same sourceCommandId queue item or real-user userInput row; complete silence triggers one same-sub recovery without synthesizing a row, creating a renderer queue, or replaying the command."
+ - "The watchdog reuses the trusted attachment's desktop-continuous or web-remote-replayable delivery boundary; it does not move conversation state into host, relay, or renderer-local queue."
+
+ - id: "state.cua-action-state-authority"
+ kind: "state-owner"
+ label: "CUA element action mutation epoch and authoritative post-state"
+ docs:
+ - "docs/cua/2026-08-18-ax-action-state-consistency.md"
+ codeSeeds:
+ - symbol: "registerMcpTools"
+ file: "apps/ccbuddy-cli/packages/core/src/mcp/index.ts"
+ - symbol: "serializeOutput"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/executor/result-serialization.ts"
+ invariants:
+ - "Only ccbuddy-cua owns pre/post state ids and mutation epochs; CCbuddy preserves or safely rejects their atomic projection."
+ - "A synced outcome and its post-state reach the model together; over-budget or malformed pairs become refresh_required."
+
+ - id: "state.message-history-model-provenance"
+ kind: "state-owner"
+ label: "Assistant model provenance in canonical model history"
+ codeSeeds:
+ - symbol: "MessageHistoryImpl"
+ file: "apps/ccbuddy-cli/packages/core/src/agent/message-history.ts"
+ - symbol: "hydrateMessageHistoryFromSession"
+ file: "apps/ccbuddy-cli/packages/core/src/agent/session-history-hydrator.ts"
+
+ - id: "state.subagent-child-runtime"
+ kind: "state-owner"
+ label: "Subagent child runtime events and tool execution"
+ codeSeeds:
+ - symbol: "createDefaultSubagentPort"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/subagent.ts"
+ - symbol: "mirrorSubagentToolEvent"
+ file: "apps/ccbuddy-cli/packages/core/src/subagent/tool-event-mirror.ts"
+
+ - id: "state.mcp-tool-catalog-cache"
+ kind: "state-owner"
+ label: "Process-scoped sanitized MCP tool catalog cache"
+ docs:
+ - "docs/mcp-async-tool-catalog-cache.md"
+
+ - id: "state.assistant-file-preview-projection"
+ kind: "state-owner"
+ label: "Turn-local assistant file preview projection"
+ docs:
+ - "docs/ui/assistant-preview-cards.md"
+ codeSeeds:
+ - symbol: "buildAssistantPreviewCardsForAssistantTextRow"
+ file: "packages/ui/src/v4/assistantPreviewCards.ts"
+ - symbol: "resolveMarkdownFileLink"
+ file: "packages/ui/src/lib/markdownFileLink.ts"
+ - symbol: "SessionPane"
+ file: "packages/ui/src/v4/SessionPane.tsx"
+
+ - id: "state.browser-context-handle-registry"
+ kind: "state-owner"
+ label: "Agent-owned BrowserControl session and tab authority"
+ docs:
+ - "docs/mcp-stateless-node-repl.md"
+ - "docs/browser-use/2026-07-28-stateless-browser-use-mcp-spec.md"
+ codeSeeds:
+ - symbol: "createProtocolBrowserControlBroker"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/browser-control-broker.ts"
+ - symbol: "requireSession"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/server-types.ts"
+
+ - id: "state.browser-screenshot-surface-coordination"
+ kind: "state-owner"
+ label: "Desktop main transient browser screenshot surface groups"
+ codeSeeds:
+ - symbol: "DesktopBrowserScreenshotSurfaceCoordinator"
+ file: "packages/desktop/src/main/browserView/browserScreenshotSurfaceCoordinator.ts"
+ - symbol: "BrowserScreenshotSurfaceCoordinator"
+ file: "packages/desktop/src/main/browserView/browserScreenshotSurfaceContracts.ts"
+
+ - id: "state.browser-recording-job-registry"
+ kind: "state-owner"
+ label: "Desktop main transient Browser Use recording jobs"
+ docs:
+ - "docs/browser-use/2026-08-12-browser-webview-video-recording-spec.md"
+ codeSeeds:
+ - symbol: "BrowserGuestManager"
+ file: "packages/desktop/src/main/browserView/browserGuestManager.ts"
+ - file: "packages/desktop/src/main/browserView/browserVideoRecorder.ts"
+
+ - id: "state.embedded-browser-resize-gesture"
+ kind: "state-owner"
+ label: "Renderer-local responsive viewport pointer gesture"
+ codeSeeds:
+ - symbol: "ResponsiveBrowserViewport"
+ file: "packages/ui/src/browser-use/ResponsiveBrowserViewport.tsx"
+
+ - id: "state.embedded-browser-fit-presentation"
+ kind: "state-owner"
+ label: "Renderer-local responsive Fit canvas presentation"
+ docs:
+ - "docs/browser-use/2026-07-15-responsive-browser-viewport-spec.md"
+ codeSeeds:
+ - symbol: "ResponsiveBrowserViewport"
+ file: "packages/ui/src/browser-use/ResponsiveBrowserViewport.tsx"
+ - symbol: "resolveBrowserViewportScale"
+ file: "packages/ui/src/browser-use/browserViewportZoom.ts"
+
+ - id: "state.embedded-browser-human-viewport-preference"
+ kind: "state-owner"
+ label: "App-global human Browser viewport presentation preference"
+ docs:
+ - "docs/browser-use/2026-07-15-responsive-browser-viewport-spec.md"
+ codeSeeds:
+ - symbol: "useResponsiveBrowserViewportControl"
+ file: "packages/ui/src/browser-use/useResponsiveBrowserViewportControl.ts"
+ - symbol: "useSettings"
+ file: "packages/ui/src/hooks/useSettingService.ts"
+
+ - id: "state.browser-tab-residency"
+ kind: "state-owner"
+ label: "Process-local logical Browser tab catalog and LRU activity"
+ docs:
+ - "docs/browser-use/2026-07-31-browser-tab-residency-budget-spec.md"
+ codeSeeds:
+ - symbol: "BrowserUseSidePaneTab"
+ file: "packages/ui/src/lib/workspaceSidePane.ts"
+ - symbol: "BrowserGuestManager"
+ file: "packages/desktop/src/main/browserView/browserGuestManager.ts"
+
+ - id: "state.automation-form-draft"
+ kind: "state-owner"
+ label: "Automation edit React draft"
+ codeSeeds:
+ - symbol: "AutomationEditView"
+ file: "packages/ui/src/settings/AutomationEditView.tsx"
+
+ - id: "state.off-peak-turn-attribution"
+ kind: "state-owner"
+ label: "Turn-scoped Off-Peak telemetry attribution"
+ codeSeeds:
+ - symbol: "backgroundSeedFromTurnStarted"
+ file: "packages/ui/src/v4/telemetry/conversationTelemetrySupervisor.ts"
+ - symbol: "turnStartedFactSchema"
+ file: "packages/shared/src/ccbuddy-protocol-v4/telemetry.ts"
+
+ - id: "state.coding-plan-purchase-funnel"
+ kind: "state-owner"
+ label: "Coding Plan purchase funnel context"
+ codeSeeds:
+ - symbol: "createCodingPlanFunnelContext"
+ file: "packages/ui/src/lib/codingPlanFunnelTelemetry.ts"
+ - symbol: "CodingPlanUpgradeDialogProvider"
+ file: "packages/ui/src/settings/CodingPlanUpgradeDialogProvider.tsx"
+ - symbol: "resolveCodingPlanEntryPlanStateFromProviderSettings"
+ file: "packages/ui/src/lib/codingPlanFunnelTelemetry.ts"
+ invariants:
+ - "Individual and Team entitled account Providers are Coding Plan; Start remains separate. Enabled/model readiness does not imply subscription ownership."
+ - "Provider ID compatibility is confined to event builders; actual Registry, selection, auth, and telemetry source facts remain unchanged."
+
+ - id: "state.subagent-form-draft"
+ kind: "state-owner"
+ label: "Subagent form draft"
+ codeSeeds:
+ - symbol: "SubagentForm"
+ file: "packages/ui/src/settings/SubagentsSection.tsx"
+
+ - id: "state.renderer-model-selection-read"
+ kind: "state-owner"
+ label: "Renderer target-host model selection read lifecycle"
+ codeSeeds:
+ - symbol: "useModelSelectionServiceView"
+ file: "packages/ui/src/hooks/useModelSelectionView.ts"
+ - symbol: "useModelSelectionView"
+ file: "packages/ui/src/hooks/useModelSelectionView.ts"
+
+ - id: "state.oauth-pending-callback"
+ kind: "state-owner"
+ label: "OAuth pending callback lifecycle"
+ codeSeeds:
+ - symbol: "OAuthService"
+ file: "packages/services/src/oauth/oauthService.ts"
+
+ - id: "state.provider-endpoint-routing-snapshot"
+ kind: "state-owner"
+ label: "Process-local routing registry; dynamic implementations own their snapshot"
+ codeSeeds:
+ - symbol: "resolveProcessProviderEndpointRoutingPort"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/provider-endpoint-routing.ts"
+
+ - id: "service.model-provider"
+ kind: "service"
+ label: "Model provider service and registry"
+ codeSeeds:
+ - symbol: "IModelProviderService"
+ file: "packages/services/src/model-provider/modelProvider.ts"
+
+ - id: "service.provider-settings"
+ kind: "service"
+ label: "Target Environment provider settings service"
+ codeSeeds:
+ - symbol: "ProviderSettingsFacade"
+ file: "packages/provider/src/facades.ts"
+ - symbol: "ProviderConfigService"
+ file: "packages/provider/src/config-service.ts"
+ - symbol: "IProviderSettingsService"
+ file: "packages/services/src/model-provider/providerFacadeServices.ts"
+ - symbol: "useProviderSettingsServiceView"
+ file: "packages/ui/src/hooks/useProviderSettingsView.ts"
+
+ - id: "persistence.model-provider-config"
+ kind: "persistence"
+ label: "Sparse Personal provider membership, order and exact model configuration"
+ docs:
+ - "docs/working-memory/provider-refactor/design/registry/settings.md"
+ codeSeeds:
+ - symbol: "NodePersonalProviderConfigRepository"
+ file: "packages/provider-node/src/personal-provider-config-repository.ts"
+
+ - id: "service.workspace-file-existence-check"
+ kind: "service"
+ label: "Workspace Host cached file existence check"
+ docs:
+ - "docs/ui/assistant-preview-cards.md"
+ codeSeeds:
+ - symbol: "createFileService"
+ file: "packages/services/src/file/fileService.ts"
+
+ - id: "service.provider-endpoint-routing"
+ kind: "service"
+ label: "CCbuddy CLI provider endpoint routing service"
+ codeSeeds:
+ - symbol: "createProviderEndpointRoutingPort"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/endpoint-routing-edition/index.ts"
+ - symbol: "createProviderEndpointRoutingFetch"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/provider-endpoint-routing-fetch.ts"
+
+ - id: "service.active-session-model-limits"
+ kind: "service"
+ label: "Active session model limit synchronization"
+ codeSeeds:
+ - symbol: "refreshActiveWorkspaceSessionModelLimits"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/active-session-model-limits.ts"
+ - symbol: "emitModelSelected"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/steering.ts"
+
+ - id: "service.automation"
+ kind: "service"
+ label: "Automation service"
+ codeSeeds:
+ - symbol: "AutomationService"
+ file: "packages/services/src/session/automationService.ts"
+
+ - id: "service.host-model-selection"
+ kind: "service"
+ label: "Target Workspace Host model selection service"
+ docs:
+ - "docs/working-memory/provider-refactor/steps/todo-13-target-host-model-selection-authority.md"
+ - "docs/working-memory/provider-refactor/steps/todo-34-model-selection-async-state-and-repo-wiki-draft.md"
+ codeSeeds:
+ - symbol: "IModelSelectionService"
+ file: "packages/services/src/model-provider/providerFacadeServices.ts"
+ - symbol: "useWorkspaceServicesResolution"
+ file: "packages/ui/src/hooks/useWorkspaceServices.tsx"
+ - symbol: "useModelSelectionServiceView"
+ file: "packages/ui/src/hooks/useModelSelectionView.ts"
+ - file: "packages/desktop/src/host/remoteWorkspaceServiceCollection.ts"
+
+ - id: "service.subagents"
+ kind: "service"
+ label: "Subagents service"
+ codeSeeds:
+ - symbol: "ISubagentsService"
+ file: "packages/services/src/subagents/subagents.ts"
+
+ - id: "service.conversation-row-actions"
+ kind: "service"
+ label: "Conversation row action command authority"
+ codeSeeds:
+ - symbol: "resolveRowActionTarget"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/product-projection.ts"
+ - symbol: "editUserQuery"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/commands/handlers/fork-edit-retry.ts"
+
+ - id: "service.selection-side-session-creation"
+ kind: "service"
+ label: "Selection side session command and atomic conversation fork"
+ codeSeeds:
+ - symbol: "createSelectionSideSession"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/commands/handlers/selection-side-session.ts"
+ - symbol: "createSelectionSideConversation"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/session-fork.ts"
+
+ - id: "service.project-memory-extraction-lifecycle"
+ kind: "service"
+ label: "Project Memory Extraction scheduler lifecycle"
+ codeSeeds:
+ - symbol: "createMemoryExtractionScheduler"
+ file: "apps/ccbuddy-cli/packages/core/src/memory/extraction.ts"
+ - symbol: "scheduleProjectMemoryExtraction"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/helpers/project-memory-extraction.ts"
+ - symbol: "createSessionFacade"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/session-facade.ts"
+
+ - id: "service.headless-cli-prompt-runtime"
+ kind: "service"
+ label: "Headless CLI prompt and target runtime"
+ codeSeeds:
+ - symbol: "runPrompt"
+ file: "apps/ccbuddy-cli/packages/cli/src/prompt-command.ts"
+ - symbol: "run"
+ file: "apps/ccbuddy-cli/packages/cli/src/run.ts"
+
+ - id: "service.project-memory-catalog"
+ kind: "service"
+ label: "Local Project Memory read-only catalog"
+ codeSeeds:
+ - symbol: "IMemoryService"
+ file: "packages/services/src/memory/memory.ts"
+ - symbol: "createMemoryService"
+ file: "packages/services/src/memory/memoryService.ts"
+
+ - id: "service.subagent-interaction-broker"
+ kind: "service"
+ label: "Subagent parent-routed blocking interaction broker"
+ codeSeeds:
+ - symbol: "createSubagentInteractionBroker"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/helpers/subagent-interaction-broker.ts"
+ - symbol: "createProtocolInteractionBroker"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/interaction-broker.ts"
+
+ - id: "service.interaction-auto-resolution-runtime"
+ kind: "service"
+ label: "CLI interaction auto-resolution registry"
+ codeSeeds:
+ - symbol: "V4InteractionRegistry"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/interaction-registry.ts"
+ - symbol: "syncAppRuntimePreferences"
+ file: "packages/services/src/ccbuddy-agent/ccbuddyAgentService.ts"
+ - symbol: "createCCbuddyAgentService"
+ file: "packages/services/src/ccbuddy-agent/ccbuddyAgentService.ts"
+ - symbol: "createBotRemoteWorkspaceService"
+ file: "packages/services/src/bots/botRemoteWorkspaceBridge.ts"
+ - symbol: "syncAppRuntimePreferences"
+ file: "packages/services/src/bots/botsService.ts"
+
+ - id: "service.conversation-rows-range"
+ kind: "service"
+ label: "Conversation read-only rows range query"
+ codeSeeds:
+ - symbol: "loadAllOlder"
+ file: "packages/ui/src/v4/conversationProjectionStore.ts"
+ - symbol: "getRowsRange"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/conversation-topic-publisher.ts"
+
+ - id: "service.session-runtime-activation"
+ kind: "service"
+ label: "Persisted session runtime activation"
+ docs:
+ - "docs/v4-refactor/17-cold-resume-runtime-activation.md"
+ codeSeeds:
+ - symbol: "activateSessionForResume"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/server-operations.ts"
+ - symbol: "ColdSessionResumeCoordinator"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/cold-session-resume.ts"
+ - symbol: "createConversationV4Gateway"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/v4-bridge.ts"
+
+ - id: "service.persisted-remote-session-repair"
+ kind: "service"
+ label: "Persisted remote session identity and path repair"
+ docs:
+ - "docs/v4-refactor/14-sessions-index.md"
+ codeSeeds:
+ - symbol: "repairPersistedRemoteSessionPaths"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/helpers/persisted-remote-session-path-repair.ts"
+ - symbol: "claimLegacySessionWorkspace"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/session-store/repositories/sessions.ts"
+ - symbol: "activateSessionForResume"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/server-operations.ts"
+
+ - id: "service.mcp-session-runtime"
+ kind: "service"
+ label: "Session-scoped live MCP runtime"
+ codeSeeds:
+ - symbol: "McpPort"
+ file: "apps/ccbuddy-cli/packages/contracts/src/interfaces/mcp.port.ts"
+ - symbol: "createMcpConnectionPool"
+ file: "apps/ccbuddy-cli/packages/adapters/src/mcp/pool.ts"
+
+ - id: "service.shared-browser-use-mcp-runtime"
+ kind: "service"
+ label: "Workspace-scoped shared stateless node_repl MCP runtime"
+ docs:
+ - "docs/mcp-stateless-node-repl.md"
+ - "docs/browser-use/2026-07-28-stateless-browser-use-mcp-spec.md"
+ codeSeeds:
+ - symbol: "NodeMcpAdapter"
+ file: "apps/ccbuddy-cli/packages/adapters/src/mcp/index.ts"
+ - symbol: "createMcpConnectionPool"
+ file: "apps/ccbuddy-cli/packages/adapters/src/mcp/pool.ts"
+
+ - id: "service.oauth"
+ kind: "service"
+ label: "OAuth service"
+ codeSeeds:
+ - symbol: "OAuthService"
+ file: "packages/services/src/oauth/oauthService.ts"
+
+ - id: "service.telemetry-core"
+ kind: "service"
+ label: "Telemetry core report pipeline"
+ codeSeeds:
+ - symbol: "createTelemetryCore"
+ file: "packages/services/src/telemetry/telemetryCore.ts"
+
+ - id: "service.cli-agent-telemetry"
+ kind: "service"
+ label: "CCbuddy CLI agent OTLP telemetry"
+ codeSeeds:
+ - symbol: "AgentExecutionTelemetryRuntime"
+ file: "apps/ccbuddy-cli/packages/telemetry/src/agent-trace-runtime.ts"
+ - symbol: "RuntimeTelemetryFacade"
+ file: "apps/ccbuddy-cli/packages/core/src/telemetry/runtime-telemetry.ts"
+
+ - id: "service.off-peak-task"
+ kind: "service"
+ label: "Off-Peak ticket and local task creation boundary"
+ codeSeeds:
+ - symbol: "OffPeakTaskService"
+ file: "packages/services/src/session/offPeakTaskService.ts"
+ - symbol: "useOffPeakTaskStore"
+ file: "packages/ui/src/store/offPeakTaskStore.ts"
+ invariants:
+ - "Provider start only waits for readiness; off-peak credentials explicitly read the latest completed Registry snapshot."
+ - "Initialization, connection changes, completed Registry notifications and manual refresh share serialized eligibility checks; support and availability publish together only for the latest generation."
+
+ - id: "service.cli-device-mid"
+ kind: "service"
+ label: "CCbuddy CLI deviceMid ensure"
codeSeeds:
- - file: packages/ui/src/v4/composer/V4ComposerToolbar.tsx
- symbol: V4ComposerModelControlsImpl
- - file: packages/ui/src/v4/SessionPane.tsx
- symbol: handleSelectModel
- - id: surface.automation-edit
- kind: ui-surface
- label: Automation edit model control
+ - symbol: "ensureCliDeviceMid"
+ file: "apps/ccbuddy-cli/packages/adapters/src/device/cli-device-mid.ts"
+
+ - id: "service.anthropic-request-options"
+ kind: "service"
+ label: "Anthropic model request metadata options"
codeSeeds:
- - file: packages/ui/src/settings/AutomationEditView.tsx
- symbol: AutomationEditView
- - file: packages/ui/src/settings/AutomationsSection.tsx
- symbol: handleEditSubmit
- - id: surface.user-subagent-form
- kind: ui-surface
- label: User Subagent form model control
+ - symbol: "createAnthropicRequestMetadataUserId"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/anthropic-request-metadata.ts"
+ - symbol: "createGenerateTextOptions"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-options.ts"
+ - symbol: "createStreamTextOptions"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-options.ts"
+
+ - id: "service.model-request-session-type-attribution"
+ kind: "service"
+ label: "Runtime model request session type attribution"
codeSeeds:
- - symbol: SubagentForm
- file: packages/ui/src/settings/SubagentsSection.tsx
- - id: surface.builtin-subagent-model
- kind: ui-surface
- label: Built-in and plugin Subagent model override
- codeSeeds:
- - symbol: SubagentModelOverrideControl
- file: packages/ui/src/settings/SubagentsSection.tsx
- - id: shared-ui.model-config-select
- kind: shared-ui
- label: ModelConfigSelect
- codeSeeds:
- - symbol: ModelConfigSelect
- file: packages/ui/src/ModelConfigSelect.tsx
- - id: shared-ui.model-select-groups
- kind: shared-ui
- label: Model selection option builder
- codeSeeds:
- - symbol: buildRegistryModelSelectGroups
- file: packages/ui/src/lib/modelSelectionGroups.ts
- - id: state.renderer-model-selection-read
- kind: state-owner
- label: Renderer target-host model selection read lifecycle
- codeSeeds:
- - symbol: useModelSelectionServiceView
- file: packages/ui/src/hooks/useModelSelectionView.ts
- - symbol: useModelSelectionView
- file: packages/ui/src/hooks/useModelSelectionView.ts
- - id: service.host-model-selection
- kind: service
- label: Target Workspace Host model selection service
- codeSeeds:
- - file: packages/services/src/model-provider/providerFacadeServices.ts
- symbol: IModelSelectionService
- - file: packages/ui/src/hooks/useWorkspaceServices.tsx
- symbol: useWorkspaceServicesResolution
- - id: capability.automations
- kind: capability
- label: Scheduled automations
+ - symbol: "resolveModelRequestSessionTypeFromTaskType"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/model-request-session-type.ts"
+ - symbol: "resolveModelRequestSessionType"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-status.ts"
+ - symbol: "createModelRequestAttributionHeaders"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-status.ts"
+
+ - id: "service.anthropic-reasoning-history-projection"
+ kind: "service"
+ label: "Anthropic request-local reasoning history projection"
+ codeSeeds:
+ - symbol: "AiSdkModelAdapter"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner.ts"
+ - symbol: "normalizeReasoningHistory"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/reasoning-history-normalization.ts"
+ - symbol: "repairReasoningHistoryAfterSignatureRejection"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/reasoning-history-normalization.ts"
+ - symbol: "runGenerateText"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-generate.ts"
+ - symbol: "runStreamText"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-stream.ts"
+
+ - id: "service.model-reasoning-request-projection"
+ kind: "service"
+ label: "Model request-local reasoning content projection"
+ codeSeeds:
+ - symbol: "toAiSdkMessages"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/transform.ts"
+ - symbol: "withOpenAiCompatibleReasoningContent"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/reasoning-content-compat.ts"
+ - symbol: "withDeepSeekV4AnthropicThinkingReplay"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/anthropic-reasoning-metadata.ts"
+
+ - id: "service.usage-stats-bridge"
+ kind: "service"
+ label: "Host App Usage protocol bridge"
+ codeSeeds:
+ - symbol: "createUsageStatsService"
+ file: "packages/services/src/usage-stats/usageStatsService.ts"
+ - symbol: "getAppUsageStats"
+ file: "packages/services/src/ccbuddy-agent/ccbuddyAgentService.ts"
+
+ - id: "service.plan-entitlements"
+ kind: "service"
+ label: "Plan identity, subscription, quota, and monitor services"
+ codeSeeds:
+ - symbol: "BigModelUsageQuotaProvider"
+ file: "packages/services/src/usage-stats/providers/bigmodelUsageQuotaProvider.ts"
+ - symbol: "createCodingPlanSubscriptionService"
+ file: "packages/services/src/coding-plan-subscription/codingPlanSubscriptionService.ts"
+
+ - id: "service.office-document-platform"
+ kind: "service"
+ label: "Planned workspace-scoped Office parsing, conversion, and save service"
+
+ - id: "service.document-skills"
+ kind: "service"
+ label: "Official DOCX, PDF, PPTX, and XLSX document skills with judge subagent"
+ docs:
+ - "docs/document-skills-plugin.md"
+ codeSeeds:
+ - symbol: "OFFICIAL_PLUGIN_DEFINITIONS"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/official-plugin-definitions.ts"
+ - symbol: "collectFilesystemPluginFiles"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/bundled-plugins.ts"
+ - symbol: "collectSeaOfficialPluginAssets"
+ file: "apps/ccbuddy-cli/packages/cli/scripts/sea-official-plugin-assets.mjs"
+ - symbol: "enumeratePluginComponents"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/plugin-components.ts"
+ - symbol: "loadPluginAgentProfiles"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/subagents.ts"
+ invariants:
+ - "Every official packaging and seed whitelist preserves agents/, and document-skills completeness requires agents/judge.md before a cache is reusable."
+
+ - id: "service.pptx-reference-resolver"
+ kind: "service"
+ label: "Official marketplace-aligned PPTX production skill"
+ codeSeeds:
+ - file: "apps/ccbuddy-cli/packages/document-skills-plugin/agents/judge.md"
+
+ - id: "service.cua-platform"
+ kind: "service"
+ label: "Desktop-local Computer Use Helper, trust broker, observation, and native input"
aliases:
- - automation
- - scheduled task
- - cron
- - 自动化
- - 定时任务
+ - "Helper lifecycle"
+ - "CUA Helper health check"
+ - "CUA Helper recovery"
+ docs:
+ - "docs/cua-permission-broker/2026-09-02-helper-lifecycle-agent-runtime-isolation-spec.md"
+ - "docs/testing/cua-e2e-case-catalog.md"
codeSeeds:
- - file: packages/services/src/session/automationService.ts
- symbol: AutomationService
- - id: state.automation-form-draft
- kind: state-owner
- label: Automation edit React draft
- codeSeeds:
- - symbol: AutomationEditView
- file: packages/ui/src/settings/AutomationEditView.tsx
- - id: service.automation
- kind: service
- label: Automation service
- codeSeeds:
- - symbol: AutomationService
- file: packages/services/src/session/automationService.ts
- - id: persistence.automation-record
- kind: persistence
- label: Persisted automation record
- codeSeeds:
- - file: packages/services/src/session/automationRepo.ts
- symbol: AutomationRepo
- - id: capability.subagents
- kind: capability
- label: Subagents
- aliases:
- - subagent
- - agent profile
- - 子代理
- - 智能体配置
- codeSeeds:
- - file: packages/ui/src/settings/SubagentsSection.tsx
- symbol: SubagentsSection
- - file: apps/ccbuddy-cli/packages/core/src/tool/handlers/agent.ts
- symbol: agentHandler
- - id: state.subagent-form-draft
- kind: state-owner
- label: Subagent form draft
- codeSeeds:
- - symbol: SubagentForm
- file: packages/ui/src/settings/SubagentsSection.tsx
- - id: service.subagents
- kind: service
- label: Subagents service
- codeSeeds:
- - file: packages/services/src/subagents/subagents.ts
- symbol: ISubagentsService
- - file: packages/services/src/subagents/subagentsService.ts
- symbol: createSubagentsService
- - id: persistence.subagent-markdown
- kind: persistence
- label: User agent Markdown, built-in and plugin agent overrides
- codeSeeds:
- - file: packages/services/src/subagents/subagentsService.ts
- symbol: createSubagentsService
- - file: packages/services/src/subagents/subagentStorage.ts
- symbol: resolveSubagentStateFile
- - id: capability.conversation-runtime
- kind: capability
- label: Conversation and session runtime
- aliases:
- - conversation
- - chat
- - session
- - turn steer
- - guide
- - follow-up queue
- - 对话
- - 会话
- - 引导
- - 消息队列
- codeSeeds:
- - file: apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/v4-gateway.ts
- symbol: ConversationV4Gateway
- - file: apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/commands/handlers/session-flow.ts
- symbol: sendText
- - id: state.conversation-projection
- kind: state-owner
- label: Client conversation projection and pending optimistic overlay
+ - symbol: "createDefaultCuaProductHelper"
+ file: "packages/services/src/node.ts"
+ - symbol: "buildCuaProductHelperAgentEnv"
+ file: "packages/services/src/node.ts"
+ - symbol: "ICuaPermissionService"
+ file: "packages/services/src/cua-permission-broker/cuaPermissionService.ts"
+ - symbol: "WindowsCuaHelperHost"
+ file: "packages/services/src/cua-permission-broker/windowsCuaDevHelperHost.ts"
+ - symbol: "resolveWindowsCuaRuntime"
+ file: "packages/services/src/cua-permission-broker/windowsCuaDevRuntime.ts"
+ invariants:
+ - "Default services assembly never schedules periodic Helper health probes or automatic recovery; health checks and recovery run only at explicit CUA demand boundaries."
+ - "Helper readiness, health failure, and recovery never dispose or restart Agent runtime."
+
+ - id: "state.windows-cua-operation-indicator"
+ kind: "state-owner"
+ label: "Windows CUA tool-triggered operation indicator projection"
+ docs:
+ - "docs/superpowers/specs/2026-08-04-windows-cua-operation-indicator-design.md"
+ - "docs/testing/cua-e2e-case-catalog.md"
+ codeSeeds:
+ - symbol: "createWindowsCuaOperationIndicator"
+ file: "packages/desktop/src/main/windowsCuaOperationIndicator.ts"
+ - symbol: "createCuaOperationTurnTracker"
+ file: "packages/services/src/ccbuddy-agent/cuaOperationTurnTracker.ts"
+
+ - id: "service.cua-helper-build-release"
+ kind: "service"
+ label: "Canonical CCbuddy Computer Use Helper build and release"
+ aliases:
+ - "cua-helper-build"
+ - "CUA Helper packaging"
+ docs:
+ - "docs/superpowers/specs/2026-08-19-cua-producer-0.5.7-integration.md"
+ - "docs/desktop/macos-dmg-signing-notarization.md"
+ codeSeeds:
+ - file: "scripts/build-cua-helper-app.mjs"
+ - file: "scripts/package-cua-helper-release.mjs"
+ - file: "packages/desktop/electron-builder.config.js"
+ - file: "packages/desktop/src/main/desktopCuaHelperInstaller.ts"
+
+ - id: "state.cua-frame-pixel-authority"
+ kind: "state-owner"
+ label: "CUA immutable raster, WindowServer provenance, and native pixel dispatch authority"
+ docs:
+ - "docs/superpowers/specs/2026-08-14-cua-final-raster-integrity-contract.md"
+ - "docs/cua/2026-08-18-frame-pixel-zoom-regression.md"
codeSeeds:
- - file: packages/ui/src/v4/conversationProjectionStore.ts
- symbol: ConversationProjectionStore
+ - file: "apps/ccbuddy-cli/packages/ccbuddy-cua-plugin/upstream.json"
+ - file: "apps/ccbuddy-cli/packages/core/src/mcp/index.ts"
invariants:
- - Snapshots replace state; only contiguous deltas apply. Gaps require resync, and pending optimistic
- commands are not accepted runtime facts.
- - id: capability.workspace-identity
- kind: capability
- label: Workspace identity and remote runtime isolation
- aliases:
- - workspace identity
- - remote workspace
- - SSH
- - WSL
- - Docker
- - 工作区隔离
- codeSeeds:
- - file: packages/ui/src/lib/remoteWorkspaceHistory.ts
- symbol: buildRemoteWorkspaceIdentity
- - file: packages/services/src/ccbuddy-agent/ccbuddyAgentConnectionScope.ts
- symbol: workspaceKey
- - id: service.workspace-identity-builder
- kind: service
- label: Remote workspace identity builder
- codeSeeds:
- - symbol: buildRemoteWorkspaceIdentity
- file: packages/ui/src/lib/remoteWorkspaceHistory.ts
- - id: boundary.workspace-key
- kind: delivery-boundary
- label: workspaceIdentity fallback isolation key
- codeSeeds:
- - file: packages/ui/src/lib/remoteWorkspaceHistory.ts
- symbol: buildWorkspaceSessionKey
- - file: packages/services/src/session/taskIndexRepo.ts
- symbol: workspaceKey
- invariants:
- - Use workspaceIdentity?.trim() || workspacePath for isolation; workspacePath remains the execution
- and display path.
- - id: persistence.task-index
- kind: persistence
- label: Task sqlite index
- codeSeeds:
- - symbol: TaskIndexRepo
- file: packages/services/src/session/taskIndexRepo.ts
- - id: service.ccbuddy-agent-connection-scope
- kind: service
- label: Host-trusted CCbuddy Agent attachment connection scope
- codeSeeds:
- - symbol: createCCbuddyAgentConnectionScope
- file: packages/services/src/ccbuddy-agent/ccbuddyAgentConnectionScope.ts
- - id: boundary.desktop-continuous
- kind: delivery-boundary
- label: Desktop continuous delivery
+ - "Official CUA raster bytes and adjacent image_ref reach the model unchanged."
+ - "Coordinate strategy=event uses WindowServer provenance and never requires AX hit-test."
+ - "Window, full-display, and nested Zoom pixels share one final native stale/occlusion gate."
+
+ - id: "service.cua-plugin-dev-runtime-assets"
+ kind: "service"
+ label: "CUA SDK/skill Dev build, validation, and filesystem seed assets"
+ docs:
+ - "docs/cua-permission-broker/cua-dev-mcp-runtime-assets.md"
+ codeSeeds:
+ - file: "scripts/build-desktop-agent-cli.mjs"
+ - file: "apps/ccbuddy-cli/packages/ccbuddy-cua-plugin/scripts/computer-use-client.mjs"
+ - symbol: "resolveOfficialPluginRoots"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/bundled-plugins.ts"
+
+ - id: "service.cua-node-repl-bridge"
+ kind: "service"
+ label: "Shared node_repl Computer Use bridge"
+ docs:
+ - "docs/cua/2026-08-24-cua-node-repl-sdk-migration-spec.md"
+ codeSeeds:
+ - symbol: "createNodeReplMcpRuntime"
+ file: "apps/ccbuddy-cli/packages/node-repl-host/src/server.ts"
+ - symbol: "createComputerUseBridgeGlobals"
+ file: "apps/ccbuddy-cli/packages/node-repl-host/src/cua-bridge.ts"
+ - symbol: "createNodeReplCuaBroker"
+ file: "apps/ccbuddy-cli/packages/node-repl-host/src/cua-broker.ts"
+ invariants:
+ - "Only node_repl js/js_add_node_module_dir/js_reset are model-visible; CUA is SDK-only."
+ - "Subagent requests are rejected and remote callers attach to an existing shared host."
+
+ - id: "state.cua-shared-host-runtime"
+ kind: "state-owner"
+ label: "Shared-host CUA runtime session, frame, and safety state"
+ docs:
+ - "docs/cua/2026-08-24-cua-node-repl-sdk-migration-spec.md"
+ codeSeeds:
+ - symbol: "createComputerUseRuntime"
+ file: "../ccbuddy-cua/src/tools/computer-use-runtime.ts"
+ - symbol: "setupComputerUseRuntime"
+ file: "apps/ccbuddy-cli/packages/ccbuddy-cua-plugin/scripts/computer-use-client.mjs"
+ invariants:
+ - "Session keys use workspaceKey + remoteSessionId + sessionId."
+ - "AccessibilitySession, frame freshness, kill switch, input holds, and possibly-sent remain runtime-owned."
+ - "Runtime state never enters relay/main, conversation snapshots, or replay queues."
+
+ - id: "service.cua-tool-contract-projection"
+ kind: "service"
+ status: "removed"
+ label: "[Removed] CUA model-facing MCP tool projection and user-title projection"
+ docs:
+ - "docs/cua-get-app-state-user-title.md"
+ codeSeeds:
+ - symbol: "registerMcpTools"
+ file: "apps/ccbuddy-cli/packages/core/src/mcp/index.ts"
+ - symbol: "CuaToolCallBlock"
+ file: "packages/ui/src/ToolCallBlocks/renderers/cua.tsx"
+
+ - id: "service.desktop-app-shutdown-policy"
+ kind: "service"
+ label: "Desktop app shutdown policy"
+ codeSeeds:
+ - symbol: "resolveAppShutdownPolicy"
+ file: "packages/desktop/src/main/appShutdownPolicy.ts"
+ - symbol: "prepareAppQuit"
+ file: "packages/desktop/src/main/index.ts"
+
+ - id: "service.data-directory-setting"
+ kind: "service"
+ label: "Data directory validation, copy, and bootstrap pointer service"
codeSeeds:
- - file: packages/shared/src/ccbuddy-protocol-v4/core.ts
- symbol: DELIVERY_PROFILES
- - file: packages/services/src/ccbuddy-agent/ccbuddyAgentConnectionScope.ts
- symbol: CCbuddyAgentV4ClientMode
+ - symbol: "createSettingService"
+ file: "packages/services/src/setting/settingService.ts"
+ - symbol: "getCCbuddyDataRootDir"
+ file: "packages/services/src/paths.ts"
+
+ - id: "service.model-io-runtime-preference-sync"
+ kind: "service"
+ label: "App-to-CLI ModelIO retention preference sync"
+ codeSeeds:
+ - symbol: "syncAppRuntimePreferences"
+ file: "packages/services/src/ccbuddy-agent/ccbuddyAgentService.ts"
+ - symbol: "updateModelIoPreferences"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/model-io-preferences.ts"
+
+ - id: "service.desktop-browser-screenshot-activity"
+ kind: "service"
+ label: "Desktop owner-renderer and target-guest bounded screenshot activity lease"
+ codeSeeds:
+ - symbol: "DesktopBrowserScreenshotActivityController"
+ file: "packages/desktop/src/main/browserView/browserScreenshotActivityController.ts"
+
+ - id: "service.desktop-chromium-network-policy"
+ kind: "service"
+ label: "Desktop main Chromium Session proxy and certificate policy"
+ docs:
+ - "apps/ccbuddy-cli/docs/design/v2/model/http-proxy.md"
+ codeSeeds:
+ - symbol: "applyDesktopChromiumNetworkPolicies"
+ file: "packages/desktop/src/main/desktopNetworkPolicy.ts"
+ - symbol: "applyDesktopSessionNetworkPolicy"
+ file: "packages/desktop/src/main/desktopNetworkPolicy.ts"
+
+ - id: "service.host-api-network-policy"
+ kind: "service"
+ label: "Window Host lifecycle-scoped app-managed API network transport"
+ docs:
+ - "docs/superpowers/specs/2026-08-25-host-api-proxy-design.md"
+ codeSeeds:
+ - symbol: "NodeApiClient"
+ file: "packages/services/src/providers/api/nodeApiClient.ts"
+ - symbol: "createLocalServices"
+ file: "packages/services/src/node.ts"
+ - symbol: "createRemoteWorkspaceServiceCollection"
+ file: "packages/desktop/src/host/remoteWorkspaceServiceCollection.ts"
+
+ - id: "service.desktop-browser-tab-residency"
+ kind: "service"
+ label: "Per-BrowserWindow logical Browser tab limit scheduler"
+ docs:
+ - "docs/browser-use/2026-07-31-browser-tab-residency-budget-spec.md"
+ codeSeeds:
+ - symbol: "BrowserGuestManager"
+ file: "packages/desktop/src/main/browserView/browserGuestManager.ts"
+
+ - id: "service.workspace-identity-builder"
+ kind: "service"
+ label: "Remote workspace identity builder"
+ codeSeeds:
+ - symbol: "buildRemoteWorkspaceIdentity"
+ file: "packages/ui/src/lib/remoteWorkspaceHistory.ts"
+
+ - id: "service.native-search-release-plan"
+ kind: "service"
+ label: "Native search platform release and ABI contract"
+ codeSeeds:
+ - symbol: "resolveNativeSearchPrebuiltPlan"
+ file: "scripts/native-search-tools-config.mjs"
+ - symbol: "verifyBuiltNativeSearchTools"
+ file: "scripts/native-search-tools-verify.mjs"
+
+ - id: "service.prompt-attachment-transfer"
+ kind: "service"
+ label: "Prompt attachment transfer service"
+ codeSeeds:
+ - symbol: "IPromptAttachmentTransferService"
+ file: "packages/services/src/prompt-attachment-transfer/promptAttachmentTransfer.ts"
+ - symbol: "createRemotePromptAttachmentTransferService"
+ file: "packages/desktop/src/host/promptAttachmentTransferService.ts"
+
+ - id: "persistence.automation-record"
+ kind: "persistence"
+ label: "Persisted automation record"
+ codeSeeds:
+ - symbol: "AutomationService"
+ file: "packages/services/src/session/automationService.ts"
+
+ - id: "persistence.usage-observability-db"
+ kind: "persistence"
+ label: "CCbuddy CLI model, turn, and tool usage facts"
+ codeSeeds:
+ - symbol: "queryAppUsage"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/session-store/repositories/usage.ts"
+ - symbol: "buildAppUsageSnapshot"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/usage-stats-builder.ts"
+
+ - id: "persistence.subagent-markdown"
+ kind: "persistence"
+ label: "User agent Markdown, built-in and plugin agent overrides"
+ codeSeeds:
+ - symbol: "ISubagentsService"
+ file: "packages/services/src/subagents/subagents.ts"
+
+ - id: "persistence.session-model-timeline"
+ kind: "persistence"
+ label: "Session model change timeline parts"
+ codeSeeds:
+ - symbol: "persistPendingModelChangeTimeline"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/timeline-persistence.ts"
+ - symbol: "synthesizeEventsFromMessages"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/transcript-hydration.ts"
+
+ - id: "persistence.session-media-artifacts"
+ kind: "persistence"
+ label: "Provider-neutral session media artifacts"
+ docs:
+ - "docs/plans/2026-08-17-video-input-support-plan.md"
+ - "apps/ccbuddy-cli/docs/design/v2/message-attachments.md"
+ - "apps/ccbuddy-cli/docs/design/v2/session-persistence.md"
+ codeSeeds:
+ - symbol: "persistToolResultMediaAttachments"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/helpers/tool-result-media-persistence.ts"
+ - symbol: "hydrateMessageHistoryFromSession"
+ file: "apps/ccbuddy-cli/packages/core/src/agent/session-history-hydrator.ts"
+ - symbol: "NodeToolArtifactStore"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/index.ts"
+
+ - id: "persistence.assistant-message-model-provenance"
+ kind: "persistence"
+ label: "Persisted assistant provider and model provenance"
+ codeSeeds:
+ - symbol: "persistAssistantMessage"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/message-persistence.ts"
+ - symbol: "hydrateMessageHistoryFromSession"
+ file: "apps/ccbuddy-cli/packages/core/src/agent/session-history-hydrator.ts"
+
+ - id: "persistence.task-index"
+ kind: "persistence"
+ label: "Task sqlite index"
+ codeSeeds:
+ - symbol: "TaskIndexRepo"
+ file: "packages/services/src/session/taskIndexRepo.ts"
+
+ - id: "persistence.app-settings"
+ kind: "persistence"
+ label: "Application settings file"
+ codeSeeds:
+ - symbol: "appSettingsSchema"
+ file: "packages/shared/src/validationAppSettings.ts"
+ - symbol: "createSettingService"
+ file: "packages/services/src/setting/settingService.ts"
+
+ - id: "persistence.model-io-jsonl"
+ kind: "persistence"
+ label: "Per-session ModelIO JSONL diagnostics"
+ codeSeeds:
+ - symbol: "writeModelIODebugRecord"
+ file: "apps/ccbuddy-cli/packages/adapters/src/model/runner-debug.ts"
+ - symbol: "readModelTrajectory"
+ file: "packages/services/src/ccbuddy-agent/modelTrajectory.ts"
+
+ - id: "persistence.project-memory-files"
+ kind: "persistence"
+ label: "Local Project Memory Markdown files"
+ codeSeeds:
+ - symbol: "resolveProjectMemoryRoot"
+ file: "apps/ccbuddy-cli/packages/core/src/memory/project-root.ts"
+ - symbol: "createMemoryService"
+ file: "packages/services/src/memory/memoryService.ts"
+
+ - id: "persistence.oauth-login-attribution"
+ kind: "persistence"
+ label: "OAuth login attribution credential"
+ codeSeeds:
+ - symbol: "OAuthCredentialRepo"
+ file: "packages/services/src/oauth/repo/oauthCredentialRepo.ts"
+
+ - id: "persistence.telemetry-device-mid"
+ kind: "persistence"
+ label: "Host telemetry state deviceMid"
+ codeSeeds:
+ - symbol: "ensureDesktopDeviceMidSync"
+ file: "packages/desktop/src/main/desktopDeviceMid.ts"
+ - symbol: "createTelemetryCore"
+ file: "packages/services/src/telemetry/telemetryCore.ts"
+ - symbol: "ensureCliDeviceMid"
+ file: "apps/ccbuddy-cli/packages/adapters/src/device/cli-device-mid.ts"
+
+ - id: "persistence.native-search-shared-deps"
+ kind: "persistence"
+ label: "Immutable native search prebuilt archives"
+ docs:
+ - "docs/runtime-tools/embedded-search-native-build.md"
+
+ - id: "persistence.browser-tab-recovery-store"
+ kind: "persistence"
+ label: "Legacy disabled Browser tab recovery catalog"
+ aliases:
+ - "browser-tab-recovery.json"
+ - "retired browser tab pageState repository"
+ docs:
+ - "docs/browser-use/2026-07-31-browser-tab-residency-budget-spec.md"
+
+ - id: "boundary.desktop-continuous"
+ kind: "delivery-boundary"
+ label: "Desktop continuous delivery"
+
- id: boundary.web-remote-replayable
kind: delivery-boundary
- label: Web remote replayable delivery
- codeSeeds:
- - file: packages/shared/src/ccbuddy-protocol-v4/core.ts
- symbol: DELIVERY_PROFILES
- - file: packages/services/src/ccbuddy-agent/ccbuddyAgentConnectionScope.ts
- symbol: CCbuddyAgentV4ClientMode
- - id: capability.composer-trigger-routing
- kind: capability
- label: Composer primary and compatibility trigger routing
+ label: Web/SSH replayable delivery
+
+ - id: "boundary.workspace-key"
+ kind: "delivery-boundary"
+ label: "workspaceIdentity fallback isolation key"
+
+ - id: "capability.composer-trigger-routing"
+ kind: "capability"
+ label: "Composer primary and compatibility trigger routing"
aliases:
- - composer prefix routing
- - mention panel routing
+ - "composer prefix routing"
+ - "mention panel routing"
- "@ and slash picker"
- - 输入框候选分组
- - 输入框触发器
- codeSeeds:
- - symbol: extractActivePromptInputTrigger
- file: packages/ui/src/lib/promptInputTriggers.ts
- - symbol: ChatPromptActionMenu
- file: packages/ui/src/prompt-editor/ChatPromptActionMenu.tsx
- - id: surface.composer-at-context-picker
- kind: ui-surface
- label: Composer @ context picker (Plugins, files, sessions, Whiteboards)
+ - "输入框候选分组"
+ - "输入框触发器"
+ docs:
+ - "docs/ui/mention-panel-layout.md"
+ codeSeeds:
+ - symbol: "extractActivePromptInputTrigger"
+ file: "packages/ui/src/lib/promptInputTriggers.ts"
+ - symbol: "ChatPromptActionMenu"
+ file: "packages/ui/src/prompt-editor/ChatPromptActionMenu.tsx"
+
+ - id: "capability.plan-slash-shortcut"
+ kind: "capability"
+ label: "App Composer /plan mode shortcut"
+ aliases:
+ - "/plan"
+ - "Plan mode composer shortcut"
+ - "Composer Plan 快捷命令"
+ docs:
+ - "docs/plan-slash-command.md"
+ - "docs/conversation-session-case-catalog.md"
+ codeSeeds:
+ - symbol: "parseV4VisibleSlashCommand"
+ file: "packages/ui/src/v4/slashCommands.ts"
+ - symbol: "SessionPane"
+ file: "packages/ui/src/v4/SessionPane.tsx"
+ - symbol: "listProtocolSlashCommands"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/slash-commands.ts"
+
+ - id: "surface.composer-at-context-picker"
+ kind: "ui-surface"
+ label: "Composer @ context picker (Plugins, files, sessions, Whiteboards)"
aliases:
- "@ context picker"
- "@ 面板"
codeSeeds:
- - symbol: MentionPlugin
- file: packages/ui/src/mentions/MentionPlugin.tsx
- - symbol: useFileMentionProvider
- file: packages/ui/src/mentions/providers/fileMentionProvider.ts
- - symbol: useSessionsMentionProvider
- file: packages/ui/src/mentions/providers/sessionsMentionProvider.ts
- - symbol: useWhiteboardMentionProvider
- file: packages/ui/src/mentions/providers/whiteboardMentionProvider.ts
- - id: surface.composer-slash-capability-picker
- kind: ui-surface
- label: Composer slash picker (commands, Skills, Subagents)
- aliases:
- - slash capability picker
- - / 面板
- codeSeeds:
- - symbol: SlashCommandPlugin
- file: packages/ui/src/SlashCommandPlugin.tsx
- - symbol: useSlashCommandMentionPanelSections
- file: packages/ui/src/slashCommandPanelSections.tsx
- - symbol: buildSlashApplyMentionPayload
- file: packages/ui/src/lib/slashApplyMentionPayload.ts
- - id: surface.composer-skill-compatibility-picker
- kind: ui-surface
- label: Composer compatible Skill picker ($ and Yen aliases)
- aliases:
- - $ Skills picker
- - $ 面板
- - ¥ 技能面板
- codeSeeds:
- - symbol: MentionPlugin
- file: packages/ui/src/mentions/MentionPlugin.tsx
- - symbol: useSkillsMentionProvider
- file: packages/ui/src/mentions/providers/skillsMentionProvider.ts
- - id: service.composer-host-file-search
- kind: service
- label: Host-owned bounded Composer file search
- codeSeeds:
- - symbol: createFileService
- file: packages/services/src/file/fileService.ts
- - id: service.conversation-command-inbox
- kind: service
- label: CLI serial command admission and idempotency
- codeSeeds:
- - file: apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/command-inbox.ts
- symbol: CommandInbox
- aliases:
- - busy input
- - running input
- - 命令接纳
- - 输入队列
- - id: state.conversation-product-projection
- kind: state-owner
- label: CLI authoritative projection of runtime events
+ - symbol: "MentionPlugin"
+ file: "packages/ui/src/mentions/MentionPlugin.tsx"
+ - symbol: "useFileMentionProvider"
+ file: "packages/ui/src/mentions/providers/fileMentionProvider.ts"
+ - symbol: "useSessionsMentionProvider"
+ file: "packages/ui/src/mentions/providers/sessionsMentionProvider.ts"
+ - symbol: "useWhiteboardMentionProvider"
+ file: "packages/ui/src/mentions/providers/whiteboardMentionProvider.ts"
+
+ - id: "service.composer-host-file-search"
+ kind: "service"
+ label: "Host-owned bounded Composer file search"
+ docs:
+ - "docs/superpowers/specs/2026-08-26-local-file-mention-stability.md"
+ codeSeeds:
+ - symbol: "createFileService"
+ file: "packages/services/src/file/fileService.ts"
+
+ - id: "surface.composer-slash-capability-picker"
+ kind: "ui-surface"
+ label: "Composer slash picker (commands, Skills, Subagents)"
+ aliases:
+ - "slash capability picker"
+ - "/ 面板"
codeSeeds:
- - file: apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/product-projection.ts
- symbol: ProductProjection
- - id: service.conversation-topic-publisher
- kind: service
- label: CLI conversation delta publication and subscription recovery
+ - symbol: "SlashCommandPlugin"
+ file: "packages/ui/src/SlashCommandPlugin.tsx"
+ - symbol: "useSlashCommandMentionPanelSections"
+ file: "packages/ui/src/slashCommandPanelSections.tsx"
+ - symbol: "buildSlashApplyMentionPayload"
+ file: "packages/ui/src/lib/slashApplyMentionPayload.ts"
+
+ - id: "surface.conversation-selection-side-slash"
+ kind: "ui-surface"
+ label: "Composer /side and /btw shortcuts with optional text input"
+ aliases:
+ - "side slash commands"
+ - "辅助对话斜杠命令"
+ docs:
+ - "docs/ui/conversation-selection-side-chat.md"
+ codeSeeds:
+ - symbol: "appSlashCommands"
+ file: "packages/ui/src/v4/SessionPane.tsx"
+ - symbol: "parseSelectionSideSlashCommand"
+ file: "packages/ui/src/v4/slashCommands.ts"
+ - symbol: "SlashCommandPlugin"
+ file: "packages/ui/src/SlashCommandPlugin.tsx"
+
+ - id: "capability.session-skill-catalog"
+ kind: "capability"
+ label: "Composer workspace/session Skill catalog lifecycle"
+ aliases:
+ - "session skill catalog"
+ - "Skill 候选冻结"
+ - "新建对话刷新 Skill"
+ docs:
+ - "docs/ui/conversation-skill-catalog-lifecycle.md"
+ - "docs/ui/mention-panel-layout.md"
+ codeSeeds:
+ - symbol: "useSkills"
+ file: "packages/ui/src/hooks/useSkills.ts"
+ - symbol: "getSkillReferenceCatalog"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/skill-reference-catalog.ts"
+
+ - id: "capability.plugin-source-acquisition"
+ kind: "capability"
+ label: "Plugin marketplace and install source acquisition"
+ aliases:
+ - "plugin source materialization"
+ - "GitHub Archive plugin install"
+ - "plugin install without git"
+ - "插件来源物化"
+ - "无 Git 安装插件"
+ docs:
+ - "docs/plugin-github-archive-source.md"
+ - "docs/claude-code-marketplace-plugin-compat.md"
+ - "docs/plugin-management-lifecycle-case-catalog.md"
+ codeSeeds:
+ - symbol: "loadMarketplaceFromSource"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/marketplace.ts"
+ - symbol: "resolvePluginSourceRoot"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/marketplace.ts"
+ - symbol: "resolveZipPluginSource"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/zip-source.ts"
+
+ - id: "service.plugin-source-materializer"
+ kind: "service"
+ label: "GitHub Archive or system Git plugin source materializer"
+ codeSeeds:
+ - symbol: "resolvePluginSourceRoot"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/marketplace.ts"
+ - symbol: "loadMarketplaceFromSource"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/marketplace.ts"
+ - symbol: "execGitCommand"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/marketplace.ts"
+
+ - id: "persistence.plugin-marketplace-installed-cache"
+ kind: "persistence"
+ label: "Known marketplace snapshots and installed plugin cache"
+ codeSeeds:
+ - symbol: "loadKnownMarketplacesSync"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/marketplace.ts"
+ - symbol: "copyMarketplaceDirectoryPlugins"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/marketplace.ts"
+ - symbol: "cacheMarketplacePlugin"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/marketplace.ts"
+
+ - id: "boundary.plugin-agent-host"
+ kind: "delivery-boundary"
+ label: "Plugin operations execute in the authoritative local or remote Agent Host"
+ codeSeeds:
+ - symbol: "updatePluginMarketplace"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/plugins.ts"
+ - symbol: "PluginSyncService"
+ file: "packages/services/src/plugin-sync/pluginSyncService.ts"
+
+ - id: "capability.workspace-project-plugin-scope"
+ kind: "capability"
+ label: "Claude-shaped User and Workspace Plugin scope with open CCbuddy config"
+ aliases:
+ - "Workspace-level Plugin"
+ - "project Plugin scope"
+ - "workspace plugin config"
+ - "项目级插件"
+ - "工作区插件"
+ docs:
+ - "docs/plugins/workspace-level-plugin-architecture.md"
+ - "docs/plugins/workspace-level-plugin-proposal-comparison.md"
+ - "docs/plugin-management-lifecycle-case-catalog.md"
+ codeSeeds:
+ - symbol: "createConfig"
+ file: "apps/ccbuddy-cli/packages/adapters/src/config/config-factory.ts"
+ - symbol: "mergeConfigs"
+ file: "apps/ccbuddy-cli/packages/adapters/src/config/config-merger.ts"
+ - symbol: "resolveStartupPlugins"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/startup-marks.ts"
+
+ - id: "surface.workspace-plugin-scope-controls"
+ kind: "ui-surface"
+ label: "Plugin User and Workspace configuration views"
+ aliases:
+ - "Plugin scope selector"
+ - "project enable"
+ - "插件作用域"
+ codeSeeds:
+ - symbol: "PluginsSection"
+ file: "packages/ui/src/settings/PluginsSection.tsx"
+ - symbol: "PluginScopeMenu"
+ file: "packages/ui/src/settings/PluginScopeMenu.tsx"
+ - symbol: "InstalledPluginDetailDialog"
+ file: "packages/ui/src/settings/InstalledPluginManagement.tsx"
+ - symbol: "requestPluginStoreOpen"
+ file: "packages/ui/src/lib/pluginStoreNavigation.ts"
+ - symbol: "setPendingSettingsPluginIntent"
+ file: "packages/ui/src/lib/settingsNavigation.ts"
+ - symbol: "PluginStorePage"
+ file: "packages/ui/src/settings/PluginStorePage.tsx"
+
+ - id: "state.workspace-plugin-effective-config"
+ kind: "state-owner"
+ label: "User and Workspace effective Plugin configuration"
+ aliases:
+ - "effective enabledPlugins"
+ - "Plugin config precedence"
+ - "插件有效配置"
+ codeSeeds:
+ - symbol: "createConfig"
+ file: "apps/ccbuddy-cli/packages/adapters/src/config/config-factory.ts"
+ - symbol: "mergeConfigs"
+ file: "apps/ccbuddy-cli/packages/adapters/src/config/config-merger.ts"
+ - symbol: "resolveEnabled"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/index.ts"
+
+ - id: "persistence.workspace-plugin-project-config"
+ kind: "persistence"
+ label: "Tracked workspace .ccbuddy/config.json Plugin declaration and sensitive options"
+ aliases:
+ - ".ccbuddy/config.json plugins"
+ - "project enabledPlugins"
+ codeSeeds:
+ - symbol: "loadProjectConfigs"
+ file: "apps/ccbuddy-cli/packages/adapters/src/config/project-config.adapter.ts"
+ - symbol: "resolvePluginConfigPathForScope"
+ file: "packages/services/src/settings-sync/settingsSyncService.ts"
+
+ - id: "boundary.workspace-plugin-trust"
+ kind: "delivery-boundary"
+ label: "Workspace Plugin trust and MCP approval gates intentionally disabled"
+ aliases:
+ - "Workspace trust"
+ - "project Plugin trust"
+ - "工作区信任"
+ - "no Workspace trust"
+ - "no Plugin MCP approval"
+ docs:
+ - "docs/plugins/workspace-level-plugin-architecture.md"
+ - "docs/security/deep-link-project-trust-boundary.md"
+
+ - id: "boundary.workspace-plugin-sensitive-config"
+ kind: "delivery-boundary"
+ label: "Plaintext Workspace Plugin credentials and secrets with output redaction"
+ aliases:
+ - "project Plugin secret"
+ - "sensitive plugin options"
+ - "项目插件凭据"
+ docs:
+ - "docs/plugins/workspace-level-plugin-architecture.md"
+ codeSeeds:
+ - symbol: "configureCCbuddyPlugin"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/plugins.ts"
+ - symbol: "resolvePluginMcpServers"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/mcp.ts"
+ - symbol: "PluginConfigControls"
+ file: "packages/ui/src/settings/PluginConfigControls.tsx"
+
+ - id: "evidence.workspace-project-plugin-cases"
+ kind: "evidence"
+ label: "Workspace Plugin lifecycle cases and coverage"
+ docs:
+ - "docs/plugin-management-lifecycle-case-catalog.md"
+ - "docs/testing/plugin-management-lifecycle-e2e-coverage-matrix.md"
+
+ - id: "evidence.plugin-source-acquisition-tests"
+ kind: "evidence"
+ label: "No-Git GitHub Archive, refresh isolation and atomic cache tests"
+ docs:
+ - "docs/testing/plugin-management-lifecycle-e2e-coverage-matrix.md"
+ codeSeeds:
+ - file: "apps/ccbuddy-cli/packages/adapters/tests/plugins.test.ts"
+ - file: "apps/ccbuddy-cli/packages/bootstrap/tests/marketplace-import-e2e.test.ts"
+
+ - id: "capability.plugin-catalog-runtime-separation"
+ kind: "capability"
+ label: "Plugin Catalog and Runtime separation for builtin uninstall/detail"
+ aliases:
+ - "builtin plugin uninstall detail"
+ - "suppressed builtin catalog"
+ - "内置插件卸载后详情"
+ - "插件 Catalog Runtime 分离"
+ docs:
+ - "docs/specs/builtin-plugin-catalog-runtime-separation.md"
+ - "docs/plugin-management-lifecycle-case-catalog.md"
+ - "docs/adr/0001-store-listing-in-marketplace-catalog.md"
+ codeSeeds:
+ - symbol: "seedBundledOfficialPlugins"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/bundled-plugins.ts"
+ - symbol: "writeOfficialMarketplace"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/bundled-plugins.ts"
+ - symbol: "uninstallCCbuddyMarketplacePlugin"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/plugins.ts"
+ - symbol: "describeMarketplacePlugin"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/marketplace.ts"
+ - symbol: "discoverNodePluginsSync"
+ file: "apps/ccbuddy-cli/packages/adapters/src/plugins/index.ts"
+ - symbol: "buildStoreItems"
+ file: "packages/ui/src/settings/pluginStoreListing.ts"
+ - symbol: "PluginStoreDetailView"
+ file: "packages/ui/src/settings/PluginStoreDetailView.tsx"
+
+ - id: "state.plugin-runtime-suppression"
+ kind: "state-owner"
+ label: "User-owned suppressed builtin runtime filter"
+ docs:
+ - "docs/specs/builtin-plugin-catalog-runtime-separation.md"
+ codeSeeds:
+ - symbol: "suppressedBuiltins"
+ file: "apps/ccbuddy-cli/packages/adapters/src/config/schema.ts"
+ - symbol: "addSuppressedBuiltinInFileConfig"
+ file: "apps/ccbuddy-cli/packages/adapters/src/config/file-config.adapter.ts"
+ - symbol: "removeSuppressedBuiltinInFileConfig"
+ file: "apps/ccbuddy-cli/packages/adapters/src/config/file-config.adapter.ts"
+
+ - id: "service.plugin-catalog-detail"
+ kind: "service"
+ label: "Read-only Marketplace plugin detail and component enumeration"
+ docs:
+ - "docs/specs/builtin-plugin-catalog-runtime-separation.md"
+ - "docs/plugin-marketplace-ui-ux.md"
+ codeSeeds:
+ - symbol: "describeCCbuddyPlugin"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/plugins.ts"
+ - symbol: "describePlugin"
+ file: "packages/ui/src/store/pluginManagementStore.ts"
+ - symbol: "PluginStoreDetailView"
+ file: "packages/ui/src/settings/PluginStoreDetailView.tsx"
+
+ - id: "evidence.plugin-catalog-runtime-tests"
+ kind: "evidence"
+ label: "Builtin uninstall/detail parity and Catalog/Runtime separation tests"
+ docs:
+ - "docs/plugin-management-lifecycle-case-catalog.md"
+ - "docs/testing/plugin-management-lifecycle-e2e-coverage-matrix.md"
+ codeSeeds:
+ - file: "apps/ccbuddy-cli/packages/bootstrap/tests/plugins.test.ts"
+ - file: "apps/ccbuddy-cli/packages/adapters/tests/plugins.test.ts"
+ - file: "packages/ui/test/pluginStoreListing.test.ts"
+
+ - id: "capability.plugin-reference"
+ kind: "capability"
+ label: "Plugin conversation reference (@ Plugin capability hint)"
+ aliases:
+ - "plugin mention"
+ - "plugin reference"
+ - "@ plugin"
+ - "plugin_reference reminder"
+ - "插件引用"
+ - "引用插件"
+ docs:
+ - "docs/plugin-reference-mention.md"
+
+ - id: "surface.composer-mention-plugins"
+ kind: "ui-surface"
+ label: "Plugin group and chip inside the Composer @ context picker"
+ aliases:
+ - "plugins mention group"
+ - "plugin chip"
+ - "@ 面板 Plugins 分组"
+ codeSeeds:
+ - symbol: "usePluginsMentionProvider"
+ file: "packages/ui/src/mentions/providers/pluginsMentionProvider.ts"
+ - symbol: "MentionPlugin"
+ file: "packages/ui/src/mentions/MentionPlugin.tsx"
+ - symbol: "buildPluginMentionMarkdown"
+ file: "packages/ui/src/mentions/mentionMarkdown.ts"
+
+ - id: "surface.plugin-store-example-prompt-try"
+ kind: "ui-surface"
+ label: "Plugin Store Try now and example prompt entries"
+ aliases:
+ - "plugin store try"
+ - "try now"
+ - "example prompt try"
+ - "插件商店试用"
+ - "立即试用"
+ - "示例提示词胶囊"
+ codeSeeds:
+ - symbol: "PluginStoreDetailView"
+ file: "packages/ui/src/settings/PluginStoreDetailView.tsx"
+ - symbol: "handleUsePrompt"
+ file: "packages/ui/src/settings/PluginsSection.tsx"
+ - symbol: "buildPluginMentionMarkdown"
+ file: "packages/ui/src/mentions/mentionMarkdown.ts"
+
+ - id: "surface.new-task-suggested-prompts"
+ kind: "ui-surface"
+ label: "New Task draft suggested prompt prefill"
+ aliases:
+ - "draft suggested prompts"
+ - "recommended prompt pills"
+ - "New Task 推荐提示词"
+ - "草稿页推荐提示词"
+ docs:
+ - "docs/ui/conversation-draft-suggested-prompts.md"
codeSeeds:
- - file: apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/conversation-topic-publisher.ts
- symbol: ConversationTopicPublisher
- - id: state.composer-submission-intent
+ - symbol: "getRecommendedPromptPool"
+ file: "packages/ui/src/v4/featureSuggestedPrompts.ts"
+ - symbol: "getRecommendedPromptsForPane"
+ file: "packages/ui/src/v4/featureSuggestedPromptRotation.ts"
+ - symbol: "ConversationDraftSuggestedPrompts"
+ file: "packages/ui/src/v4/ConversationDraftSuggestedPrompts.tsx"
+ - symbol: "ConversationDraftSuggestedPromptsContainer"
+ file: "packages/ui/src/v4/ConversationDraftSuggestedPromptsContainer.tsx"
+ - symbol: "buildDraftSuggestedPromptPrefill"
+ file: "packages/ui/src/v4/draftSuggestedPromptPrefill.ts"
+ - symbol: "mapClientScenesToDraftSuggestedPromptItems"
+ file: "packages/ui/src/v4/draftSuggestedPromptItems.ts"
+ - symbol: "SessionPane"
+ file: "packages/ui/src/v4/SessionPane.tsx"
+
+ - id: "service.client-scenes"
+ kind: "service"
+ label: "CCbuddy Client Scenes option source"
+ aliases:
+ - "clientScenesService"
+ - "draft-suggestion scene"
+ - "scheduled-task scene"
+ - "off-peak-task scene"
+ - "推荐提示词配置"
+ docs:
+ - "docs/ccbuddy-client-scenes-api.md"
+ codeSeeds:
+ - symbol: "IClientScenesService"
+ file: "packages/services/src/client-scenes/clientScenes.ts"
+ - symbol: "createClientScenesService"
+ file: "packages/services/src/client-scenes/clientScenesService.ts"
+
+ - id: "shared-ui.automation-template-catalog"
+ kind: "shared-ui"
+ label: "Client Scenes automation template mapper and loader"
+ codeSeeds:
+ - symbol: "mapClientScenesToAutomationTemplates"
+ file: "packages/ui/src/settings/automationTemplateCatalog.ts"
+ - symbol: "useAutomationTemplates"
+ file: "packages/ui/src/settings/useAutomationTemplates.ts"
+
+ - id: "state.plugin-reference-session-catalog"
+ kind: "state-owner"
+ label: "Session-frozen plugin identity catalog"
+ aliases:
+ - "plugin reference catalog"
+ - "session plugin catalog"
+ - "冻结插件目录"
+ codeSeeds:
+ - symbol: "buildPluginReferenceCatalog"
+ file: "apps/ccbuddy-cli/packages/core/src/plugin-reference/catalog.ts"
+ - symbol: "resolveStartupPlugins"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/startup-marks.ts"
+
+ - id: "state.session-skill-catalog"
+ kind: "state-owner"
+ label: "Agent runtime-frozen Session Skill catalog"
+ aliases:
+ - "skillLoadOutcome"
+ - "Session Skill 快照"
+ codeSeeds:
+ - symbol: "getSkillCatalog"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/context.ts"
+ - symbol: "ensureContextInitialized"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/context.ts"
+
+ - id: "service.skill-reference-catalog"
+ kind: "service"
+ label: "skills/referenceCatalog protocol query (session vs workspace authority)"
+ codeSeeds:
+ - symbol: "getSkillReferenceCatalog"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/skill-reference-catalog.ts"
+ - symbol: "getSkillReferenceCatalog"
+ file: "packages/services/src/ccbuddy-agent/ccbuddyAgentService.ts"
+
+ - id: "service.plugin-reference-catalog"
+ kind: "service"
+ label: "plugins/referenceCatalog protocol query (session vs workspace authority)"
+ codeSeeds:
+ - symbol: "getPluginReferenceCatalog"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/plugins.ts"
+ - symbol: "getPluginReferenceCatalog"
+ file: "packages/services/src/ccbuddy-agent/ccbuddyAgentService.ts"
+
+ - id: "service.suggested-plugin-reference-resolution"
+ kind: "service"
+ label: "Trusted suggested-prompt Plugin refresh, install candidate resolution, and cancellation"
+ aliases:
+ - "plugins/resolveSuggestedReference"
+ - "推荐插件可信解析"
+ - "推荐提示词一键安装"
+ docs:
+ - "docs/ui/conversation-draft-suggested-prompts.md"
+ - "docs/plugin-management-lifecycle-case-catalog.md"
+ codeSeeds:
+ - symbol: "resolveSuggestedPluginReference"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/plugin-reference-catalog.ts"
+ - symbol: "withPluginOperationSignal"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/server.ts"
+ - symbol: "resolveSuggestedPluginReference"
+ file: "packages/services/src/ccbuddy-agent/ccbuddyAgentService.ts"
+
+ - id: "service.plugin-reference-reminder"
+ kind: "service"
+ label: "Turn plugin_reference reminder injection (live capability intersection)"
+ codeSeeds:
+ - symbol: "buildPluginReferenceReminderBody"
+ file: "apps/ccbuddy-cli/packages/core/src/plugin-reference/reminder.ts"
+ - symbol: "injectPluginReferenceReminderFromTurn"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/plugin-reference.ts"
+
+ - id: "persistence.plugin-reference-reminder-history"
+ kind: "persistence"
+ label: "Append-only model-only plugin_reference notices in session history"
+ codeSeeds:
+ - symbol: "persistSyntheticUserNoticeForSession"
+ file: "apps/ccbuddy-cli/packages/core/src/runtime/methods/message-persistence.ts"
+ - symbol: "hydrateMessageHistoryFromSession"
+ file: "apps/ccbuddy-cli/packages/core/src/agent/session-history-hydrator.ts"
+
+ - id: "boundary.local-development-runtime"
+ kind: "delivery-boundary"
+ label: "Local source development runtime"
+
+ - id: "boundary.packaged-desktop-runtime"
+ kind: "delivery-boundary"
+ label: "Packaged Stable or Preview desktop runtime"
+
+ - id: "boundary.ccbuddy-cli-provider-transport"
+ kind: "delivery-boundary"
+ label: "CCbuddy CLI default AI SDK provider transport"
+
+ - id: "boundary.cua-desktop-local"
+ kind: "delivery-boundary"
+ label: "macOS and Windows desktop-local CUA authorization boundary"
+
+ - id: "evidence.conversation-catalog"
+ kind: "evidence"
+ label: "Conversation case catalog and E2E matrix"
+ docs:
+ - "docs/conversation-session-case-catalog.md"
+ - "docs/testing/conversation-session-e2e-coverage-matrix.md"
+
+ - id: "evidence.conversation-fault-catalog"
+ kind: "evidence"
+ label: "Conversation environment fault catalog and coverage matrix"
+ docs:
+ - "docs/testing/conversation-session-environment-fault-catalog.md"
+ - "docs/testing/conversation-session-fault-e2e-coverage-matrix.md"
+ - "docs/testing/conversation-session-network-sse-decision-worksheet.md"
+ codeSeeds:
+ - file: "apps/ccbuddy-cli/packages/core/tests/runtime-stream-recovery.test.ts"
+ - file: "apps/ccbuddy-cli/packages/bootstrap/tests/product-projection.test.ts"
+
+ - id: "evidence.development-storage-isolation"
+ kind: "evidence"
+ label: "Development storage Docker E2E and path tests"
+ codeSeeds:
+ - file: "packages/desktop/test/e2e/conversation-session/conversation-session-development-storage-isolation.test.ts"
+ - file: "packages/shared/test/storageProfile.test.ts"
+ - file: "packages/desktop/test/desktopStorageProfile.test.ts"
+
+ - id: "evidence.model-io-full-retention-tests"
+ kind: "evidence"
+ label: "ModelIO full retention schema, UI, protocol, and writer tests"
+ codeSeeds:
+ - file: "packages/shared/test/appSettings.test.ts"
+ - file: "packages/ui/test/settingsDataBaseDirControl.test.ts"
+ - file: "packages/ui/test/useSettingServiceAutoResolution.test.ts"
+ - file: "packages/services/test/ccbuddyAgentService.test.ts"
+ - file: "apps/ccbuddy-cli/packages/bootstrap/tests/ccbuddy-protocol.test.ts"
+ - file: "apps/ccbuddy-cli/packages/adapters/tests/runner.test.ts"
+
+ - id: "evidence.model-request-session-type-tests"
+ kind: "evidence"
+ label: "Model request session type classification and transport tests"
+ codeSeeds:
+ - file: "apps/ccbuddy-cli/packages/core/tests/model-request-session-type.test.ts"
+ - file: "apps/ccbuddy-cli/packages/adapters/tests/runner-options.test.ts"
+ - file: "apps/ccbuddy-cli/packages/adapters/tests/runner.test.ts"
+
+ - id: "evidence.mcp-runtime-tests"
+ kind: "evidence"
+ label: "MCP runtime, tool bridge, and lifecycle tests"
+ codeSeeds:
+ - file: "apps/ccbuddy-cli/packages/core/tests/mcp-runtime.test.ts"
+ - file: "apps/ccbuddy-cli/packages/core/tests/mcp-tool-bridge.test.ts"
+ - file: "apps/ccbuddy-cli/packages/adapters/tests/mcp.test.ts"
+ - file: "apps/ccbuddy-cli/packages/adapters/tests/mcp-process-lifecycle.test.ts"
+ - file: "apps/ccbuddy-cli/packages/adapters/tests/mcp-version-negotiation.test.ts"
+ - file: "packages/desktop/test/upstreamReplayServer.test.ts"
+ - file: "packages/desktop/test/e2e/settings/mcp-dual-era-version-negotiation.test.ts"
+ - file: "packages/desktop/test/e2e/conversation-session/manual-review/pending/conversation-session-browser-use-iab-multisession.test.ts"
+
+ - id: capability.window-single-host-controller-r1
+ kind: capability
+ label: R1 window-scoped single Host controller
+ aliases:
+ - single window Host
+ - 窗口唯一 Host
+ - R1 Host controller
+ docs:
+ - docs/architecture/r1-single-window-host-refactor.md
+ - docs/architecture/ccbuddy-code-architecture-overview.md
+ codeSeeds:
+ - symbol: createRemoteWorkspaceSessionManager
+ file: packages/desktop/src/main/desktopRemoteSessions.ts
+ - file: packages/desktop/src/host/index.ts
+ - symbol: useGlobalTaskList
+ file: packages/ui/src/hooks/useGlobalTaskList.ts
+
+ - id: state.window-host-controller-projection
kind: state-owner
- label: Renderer model intent for the next submission
- codeSeeds:
- - file: packages/ui/src/v4/SessionPane.tsx
- symbol: handleSelectModel
- - file: packages/ui/src/v4/SessionPane.tsx
- symbol: createComposerSubmissionConfig
- - id: service.conversation-submission
- kind: service
- label: CLI sendText submission handling
- codeSeeds:
- - file: apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/commands/handlers/session-flow.ts
- symbol: sendText
- - id: service.runtime-submission-execution
- kind: service
- label: Runtime applies the admitted submission selection at execution
- codeSeeds:
- - file: apps/ccbuddy-cli/packages/core/src/runtime/methods/turn-model.ts
- symbol: applySubmissionExecutionState
+ label: Window Host workspace and task projection
+ docs:
+ - docs/architecture/r1-single-window-host-refactor.md
+ codeSeeds:
+ - symbol: useGlobalTaskList
+ file: packages/ui/src/hooks/useGlobalTaskList.ts
+ - file: packages/services/src/task-index
+
+ - id: "service.window-host-remote-connection-registry"
+ kind: "service"
+ label: "Window Host remote connection and logical session registry"
+ docs:
+ - "docs/architecture/r1-single-window-host-refactor.md"
+ codeSeeds:
+ - symbol: "createWindowRemoteConnectionRegistry"
+ file: "packages/desktop/src/host/windowRemoteConnectionRegistry.ts"
+ - symbol: "createRemoteWorkspaceSessionManager"
+ file: "packages/desktop/src/main/desktopRemoteSessions.ts"
+
+ - id: boundary.window-host-scoped-service-attachment
+ kind: boundary
+ label: Window Host local and remote scoped MessagePort attachment
+ docs:
+ - docs/architecture/r1-single-window-host-refactor.md
+ codeSeeds:
+ - file: packages/desktop/src/host/index.ts
+ - file: packages/desktop/src/renderer/src/main.tsx
+ - symbol: createRemoteWorkspaceSessionManager
+ file: packages/desktop/src/main/desktopRemoteSessions.ts
+
+ - id: "evidence.ssh-remote-p0-e2e"
+ kind: "evidence"
+ label: "Real SSH connection lifecycle and multi-workspace P0 Electron E2E"
+ docs:
+ - "docs/testing/ssh-remote-p0-e2e-plan.md"
+ - "docs/conversation-session-case-catalog.md"
+ - "docs/testing/conversation-session-e2e-coverage-matrix.md"
+ codeSeeds:
+ - file: "packages/desktop/test/e2e/conversation-session/conversation-session-ssh-remote-p0.test.ts"
+ - file: "packages/desktop/test/e2e/conversation-session/manual-review/pending/conversation-session-ssh-remote-cancel-retry.test.ts"
+
+ - id: "capability.unified-ccbuddy-lite-server"
+ kind: "capability"
+ label: "Unified CCbuddy Lite and multi-server control"
+ aliases:
+ - "CCbuddy Lite"
+ - "multi-server"
+ - "unified remote architecture"
+ - "统一多端架构"
+ - "多 Server 管理"
+ docs:
+ - "docs/architecture/unified-ccbuddy-lite-server-multi-server-plan.md"
+ - "docs/architecture/ccbuddy-code-architecture-overview.md"
+
+ - id: surface.multi-server-controller-ui
+ kind: ui-surface
+ label: Desktop and Browser multi-server view
+ docs:
+ - docs/architecture/unified-ccbuddy-lite-server-multi-server-plan.md
+ codeSeeds:
+ - symbol: Root
+ file: packages/ui/src/Root.tsx
+ - file: packages/web/src/main.tsx
+ - file: packages/desktop/src/renderer/src/main.tsx
+
+ - id: "state.controller-server-directory"
+ kind: "state-owner"
+ label: "Controller-local server connections and aggregate projection"
+ docs:
+ - "docs/architecture/unified-ccbuddy-lite-server-multi-server-plan.md"
+
+ - id: "service.ccbuddy-stable-launcher"
+ kind: "service"
+ label: "Planned stable launcher, bootstrap, and version runtime manager"
+ docs:
+ - "docs/architecture/unified-ccbuddy-lite-server-multi-server-plan.md"
+
+ - id: "service.multi-server-controller"
+ kind: "service"
+ label: "Planned Desktop and Lite multi-server connection controller"
+ docs:
+ - "docs/architecture/unified-ccbuddy-lite-server-multi-server-plan.md"
+ codeSeeds:
+ - symbol: "connectRemote"
+ file: "packages/server/src/remote/connect.ts"
+ - symbol: "createRemoteWorkspaceSessionManager"
+ file: "packages/desktop/src/main/desktopRemoteSessions.ts"
+
+ - id: "persistence.ccbuddy-server-identity"
+ kind: "persistence"
+ label: "Planned persistent server identity and server-local workspace registry"
+ docs:
+ - "docs/architecture/unified-ccbuddy-lite-server-multi-server-plan.md"
+
+ - id: "persistence.ccbuddy-resource-store"
+ kind: "persistence"
+ label: "Planned source-server prompt resource store"
+ docs:
+ - "docs/architecture/unified-ccbuddy-lite-server-multi-server-plan.md"
+ - "docs/remote-prompt-attachment-materialization.md"
+
+ - id: "boundary.unified-server-transport"
+ kind: "delivery-boundary"
+ label: "Transport-independent Server RPC over local, WebSocket, and SSH tunnel"
+ docs:
+ - "docs/architecture/unified-ccbuddy-lite-server-multi-server-plan.md"
+ - "docs/architecture/rpc.md"
+
+ - id: "evidence.browser-screenshot-surface-tests"
+ kind: "evidence"
+ label: "Browser screenshot surface and bounded activity coverage"
+ codeSeeds:
+ - file: "packages/desktop/test/browserScreenshotSurfaceCoordinator.test.ts"
+ - file: "packages/desktop/test/browserScreenshotSurfaceCoordinatorWiring.test.ts"
+ - file: "packages/desktop/test/browserScreenshotActivityController.test.ts"
+ - file: "packages/desktop/test/browserGuestManager.test.ts"
+ - file: "packages/desktop/scripts/browser-background-activity-smoke.mjs"
+
+ - id: "evidence.browser-webview-video-recording-tests"
+ kind: "evidence"
+ label: "Browser WebView recording schema, SDK, main, artifact and runtime coverage"
+ codeSeeds:
+ - file: "packages/shared/test/browserControlProtocol.test.ts"
+ - file: "apps/ccbuddy-cli/packages/core/tests/browser-client.test.ts"
+ - file: "packages/desktop/test/browserGuestManager.test.ts"
+ - file: "packages/desktop/test/browserControlMainBridge.test.ts"
+ - file: "packages/desktop/test/browserRecordingArtifactMaterializer.test.ts"
+ - file: "packages/desktop/test/browserVideoRecorder.test.ts"
+
+ - id: "evidence.embedded-browser-network-policy-tests"
+ kind: "evidence"
+ label: "Desktop Chromium Session network policy target and certificate tests"
+ docs:
+ - "docs/testing/browser-use-codex-parity-coverage-matrix.md"
+ codeSeeds:
+ - file: "packages/desktop/test/desktopNetworkPolicy.test.ts"
+
+ - id: "evidence.host-api-network-policy-tests"
+ kind: "evidence"
+ label: "Host API proxy, No Proxy, custom CA, lifecycle, and remote-authority tests"
+ docs:
+ - "docs/superpowers/specs/2026-08-25-host-api-proxy-design.md"
+ codeSeeds:
+ - file: "packages/services/test/nodeApiClient.test.ts"
+ - file: "packages/services/test/nodeApiClientEndpoint.test.ts"
+ - file: "packages/desktop/src/host/remoteWorkspaceServiceCollection.ts"
+
+ - id: "evidence.embedded-browser-responsive-viewport-tests"
+ kind: "evidence"
+ label: "Responsive browser viewport interaction coverage"
+ codeSeeds:
+ - file: "packages/ui/test/UnifiedBrowserView.test.ts"
+ - file: "packages/shared/test/appSettings.test.ts"
+ - file: "packages/desktop/test/e2e/browser-human-viewport-preference.test.ts"
+
+ - id: "evidence.browser-use-agent-guidance-tests"
+ kind: "evidence"
+ label: "Browser Use bundled and fallback agent guidance coverage"
+ codeSeeds:
+ - file: "apps/ccbuddy-cli/packages/core/tests/browser-manifest.test.ts"
+
+ - id: "evidence.browser-use-plugin-distribution-tests"
+ kind: "evidence"
+ label: "Browser Use package, manifest, seed, SEA, and cache version alignment coverage"
+ codeSeeds:
+ - file: "apps/ccbuddy-cli/packages/bootstrap/tests/plugins.test.ts"
+ - file: "apps/ccbuddy-cli/packages/bootstrap/tests/bundled-plugins-resilience.test.ts"
+ - file: "apps/ccbuddy-cli/packages/cli/tests/build-sea.test.mjs"
+ - file: "apps/ccbuddy-cli/packages/adapters/tests/plugins.test.ts"
+ - file: "apps/ccbuddy-cli/packages/node-repl-host/test/mcp-server.test.ts"
+ - file: "packages/server/test/remoteDeploy.test.ts"
+ - file: "packages/desktop/test/runtime-asset-scripts.test.ts"
+ - id: "capability.side-pane-tab-strip"
+ kind: "capability"
+ label: "Responsive Side Pane tab strip"
+ aliases:
+ - "side panel tabs"
+ - "side pane tabs"
+ - "侧边面板标签"
+ docs:
+ - "docs/ui/side-pane-tabs.md"
+ - "docs/testing/side-pane-tabs-e2e-case-catalog.md"
+ - "docs/testing/side-pane-tabs-e2e-coverage-matrix.md"
+ codeSeeds:
+ - symbol: "AnimatedSidePanePanel"
+ file: "packages/ui/src/app-shell/AnimatedSidePanePanel.tsx"
+ - symbol: "SortableSidePaneTabTrigger"
+ file: "packages/ui/src/app-shell/SidePaneTabTrigger.tsx"
+ - symbol: "SidePaneTabTitleTooltip"
+ file: "packages/ui/src/app-shell/SidePaneTabTitleTooltip.tsx"
+
+ - id: "surface.workspace-side-pane-tab-strip"
+ kind: "ui-surface"
+ label: "Workspace Side Pane tab strip"
+ codeSeeds:
+ - symbol: "AnimatedSidePanePanel"
+ file: "packages/ui/src/app-shell/AnimatedSidePanePanel.tsx"
+
+ - id: "capability.windows-window-chrome"
+ kind: "capability"
+ label: "Windows version-aware workspace window chrome"
+ aliases:
+ - "Windows rounded corners"
+ - "Windows maximize chrome"
+ - "Windows 窗口圆角"
+ docs:
+ - "docs/ui/windows-titlebar-menu.md"
+ codeSeeds:
+ - symbol: "useAppChromeState"
+ file: "packages/ui/src/app-shell/useAppChromeState.ts"
+
+ - id: "surface.workspace-shell-window-edge"
+ kind: "ui-surface"
+ label: "Workspace shell outer window edge"
+ docs:
+ - "docs/ui/windows-titlebar-menu.md"
+ codeSeeds:
+ - symbol: "WorkspaceShellLayout"
+ file: "packages/ui/src/app-shell/WorkspaceShellLayout.tsx"
+
+ - id: "state.desktop-window-chrome"
+ kind: "state-owner"
+ label: "Electron BrowserWindow maximize and Windows build state"
+ codeSeeds:
+ - symbol: "createBrowserWindow"
+ file: "packages/desktop/src/main/desktopWindowChrome.ts"
+
+ - id: "evidence.browser-tab-residency-tests"
+ kind: "evidence"
+ label: "Browser tab residency policy, persistence, lifecycle, and Electron resource evidence"
+ docs:
+ - "docs/conversation-session-case-catalog.md"
+ - "docs/testing/conversation-session-e2e-coverage-matrix.md"
+ - "docs/testing/browser-use-codex-parity-coverage-matrix.md"
+ codeSeeds:
+ - file: "packages/desktop/test/browserGuestManager.test.ts"
+ - file: "packages/ui/test/UnifiedBrowserView.test.ts"
+
+ - id: "capability.workspace-hook-persistent-trust"
+ kind: "capability"
+ label: "Workspace Hook persistent trust and runtime admission"
+ aliases:
+ - "workspace hook trust"
+ - "hook trust"
+ - "workspace hook review"
+ - "工作区 Hook 信任"
+ - "钩子信任"
+ docs:
+ - "docs/specs/workspace-hook-trust-persistent-only.md"
+ codeSeeds:
+ - symbol: "WorkspaceHookReviewController"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/workspace-hook-review-controller.ts"
+ - symbol: "useWorkspaceHookInlineTrust"
+ file: "packages/ui/src/settings/useWorkspaceHookInlineTrust.ts"
+
+ - id: "capability.conversation-hook-turn-summary"
+ kind: "capability"
+ label: "Conversation turn Hook execution summary"
+ aliases:
+ - "Hook UI"
+ - "Hook execution summary"
+ - "conversation Hook icon"
+ - "对话钩子图标"
+ - "本轮钩子"
+ docs:
+ - "docs/chat/conversation-hook-turn-summary.md"
+ codeSeeds:
+ - symbol: "ProductProjection.onHookRunLifecycle"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/product-projection.ts"
+ - symbol: "ConversationHookDetailsAction"
+ file: "packages/ui/src/v4/ConversationHookDetailsAction.tsx"
+ - symbol: "ConversationTurnGroup"
+ file: "packages/ui/src/v4/ConversationTurnGroup.tsx"
+
+ - id: "surface.conversation-turn-hook-summary"
+ kind: "ui-surface"
+ label: "Conversation terminal turn Hook action and details Popover"
+ docs:
+ - "docs/chat/conversation-hook-turn-summary.md"
+ codeSeeds:
+ - symbol: "ConversationHookDetailsAction"
+ file: "packages/ui/src/v4/ConversationHookDetailsAction.tsx"
+ - symbol: "ConversationAssistantTextActions"
+ file: "packages/ui/src/v4/ConversationRowView.tsx"
+ - symbol: "ConversationTurnGroup"
+ file: "packages/ui/src/v4/ConversationTurnGroup.tsx"
+
+ - id: "surface.workspace-hook-settings-trust-row"
+ kind: "ui-surface"
+ label: "Settings Workspace Hook trust row"
+ docs:
+ - "docs/specs/workspace-hook-trust-persistent-only.md"
+ codeSeeds:
+ - symbol: "HooksSection"
+ file: "packages/ui/src/settings/HooksSection.tsx"
+ - symbol: "HooksList"
+ file: "packages/ui/src/settings/HooksList.tsx"
+
+ - id: "capability.hooks-settings"
+ kind: "capability"
+ label: "Scoped Hooks settings and plugin Hook projection"
+ aliases:
+ - "Hooks settings"
+ - "钩子设置"
+ docs:
+ - "docs/settings-hook-list.md"
+ - "docs/hooks-runtime-contract.md"
+ - "docs/plugin-hooks-compat.md"
+ codeSeeds:
+ - symbol: "HooksSection"
+ file: "packages/ui/src/settings/HooksSection.tsx"
+ - symbol: "HooksList"
+ file: "packages/ui/src/settings/HooksList.tsx"
+ - symbol: "HookForm"
+ file: "packages/ui/src/settings/HookForm.tsx"
+
+ - id: "surface.hooks-settings"
+ kind: "ui-surface"
+ label: "Independent Hooks settings page"
+ codeSeeds:
+ - symbol: "HooksSection"
+ file: "packages/ui/src/settings/HooksSection.tsx"
+
+ - id: "state.hooks-settings-store"
+ kind: "state-owner"
+ label: "Workspace-isolated Hooks settings store"
+ codeSeeds:
+ - symbol: "useHooksStore"
+ file: "packages/ui/src/store/hooksStore.ts"
+
+ - id: "persistence.hooks-config-files"
+ kind: "persistence"
+ label: "User and workspace .ccbuddy Hook configuration files"
+ docs:
+ - "docs/hooks-runtime-contract.md"
+
+ - id: "state.workspace-hook-review-renderer-binding"
+ kind: "state-owner"
+ label: "Renderer Workspace Hook command and immutable review bindings"
+ codeSeeds:
+ - symbol: "useWorkspaceHookReviewStore"
+ file: "packages/ui/src/store/workspaceHookReviewStore.ts"
+
+ - id: "service.workspace-hook-runtime-review-controller"
+ kind: "service"
+ label: "Runtime Workspace Hook review and Trust mutation controller"
+ codeSeeds:
+ - symbol: "WorkspaceHookReviewController"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/workspace-hook-review-controller.ts"
+
+ - id: "service.workspace-hook-settings-pretrust"
+ kind: "service"
+ label: "Workspace-scoped Settings pretrust authority without a task"
+ codeSeeds:
+ - symbol: "grantWorkspaceHookTrust"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/workspace-hook-trust-cli.ts"
+ - symbol: "grantWorkspaceHookTrustForProtocol"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/workspace-hook-trust.ts"
+ - symbol: "createHooksService"
+ file: "packages/services/src/hooks/hooksService.ts"
+
+ - id: "state.workspace-hook-managed-policy"
+ kind: "state-owner"
+ label: "Trusted Host-owned Workspace Hook managed policy provider"
+ codeSeeds:
+ - symbol: "WorkspaceHookPolicyProvider"
+ file: "apps/ccbuddy-cli/packages/core/src/hooks/workspace-hook-policy.ts"
+ - symbol: "CCbuddyProtocolAgentDependencies"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol/server-types.ts"
+
+ - id: "persistence.workspace-hook-trust-store"
+ kind: "persistence"
+ label: "User-owned Workspace Hook persistent Trust store"
+ codeSeeds:
+ - symbol: "FileWorkspaceHookTrustStore"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/workspace-hook-trust-store.ts"
+
+ - id: "evidence.workspace-hook-persistent-trust-tests"
+ kind: "evidence"
+ label: "Workspace Hook Settings, controller, identity, and Trust store coverage"
+ codeSeeds:
+ - file: "packages/ui/test/hooksSection.test.ts"
+ - file: "packages/ui/test/workspaceHookInlineTrust.test.ts"
+ - file: "packages/ui/test/workspaceHookReviewStore.test.ts"
+ - file: "apps/ccbuddy-cli/packages/bootstrap/tests/workspace-hook-review-controller.test.ts"
+ - file: "apps/ccbuddy-cli/packages/adapters/tests/workspace-hook-trust-store.test.ts"
+ - id: "capability.workflow-run-confirmation"
+ kind: "capability"
+ label: "CreateWorkflow run confirmation gate with analysis preview"
+ aliases:
+ - "workflow confirmation"
+ - "run this workflow"
+ - "CreateWorkflow permission"
+ - "运行工作流确认"
+ - "工作流权限"
+ docs:
+ - "docs/dynamic-workflow/launch.md"
+ - "apps/ccbuddy-cli/packages/dynamic-workflow/docs/analysis.md"
+ codeSeeds:
+ - symbol: "createWorkflowToolEntry"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/handlers/create-workflow.ts"
+ - symbol: "resolveToolPermission"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/executor/permission-flow.ts"
+ - symbol: "buildProtocolPermissionOptions"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/permission-options.ts"
+ - symbol: "onPermissionRequested"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/ccbuddy-protocol-v4/product-projection.ts"
+
+ - id: "surface.workflow-run-confirmation-dialog"
+ kind: "ui-surface"
+ label: "Workflow run confirmation permission block (graph primary, folded script)"
+ aliases:
+ - "workflow permission block"
+ - "工作流确认弹窗"
+ codeSeeds:
+ - symbol: "WorkflowPermissionBlock"
+ file: "packages/ui/src/WorkflowPermissionBlock.tsx"
+ - symbol: "PermissionDialog"
+ file: "packages/ui/src/PermissionDialog.tsx"
+ - symbol: "pendingPermissionToLegacyRequest"
+ file: "packages/ui/src/v4/pendingInteractionAdapter.ts"
+
+ - id: "capability.workflow-live-run"
+ kind: "capability"
+ label: "Dynamic workflow live run: engine submit, background lifecycle and live progress"
+ aliases:
+ - "workflow live run"
+ - "workflow execution engine wiring"
+ - "dynamic workflow run"
+ - "workflowRuns state key"
+ - "工作流实时运行"
+ - "工作流执行引擎接线"
+ - "运行工作流"
+ docs:
+ - "docs/dynamic-workflow/presentation.md"
+ - "apps/ccbuddy-cli/packages/dynamic-workflow/docs/execution-engine.md"
+ - "apps/ccbuddy-cli/packages/dynamic-workflow/docs/analysis.md"
+ codeSeeds:
+ - symbol: "createWorkflowToolEntry"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/handlers/create-workflow.ts"
+ - symbol: "runWorkflowScript"
+ file: "apps/ccbuddy-cli/packages/dynamic-workflow-runtime/src/harness.ts"
+ - symbol: "WorkflowEngine"
+ file: "apps/ccbuddy-cli/packages/dynamic-workflow/src/engine/engine.ts"
+ - symbol: "createAgentRuntimeWorkflowDriver"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/workflow-driver.ts"
+ - symbol: "createScriptWorkflowAgentRuntime"
+ file: "apps/ccbuddy-cli/packages/bootstrap/src/app/script-workflow-child-runtime.ts"
+ - symbol: "BackgroundTaskTracker"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/executor/background-tasks.ts"
+ - symbol: "statePatchSchema"
+ file: "packages/shared/src/ccbuddy-protocol-v4/delta.ts"
+ - symbol: "createDwfJournalStore"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/session-store/repositories/dwf-journal.ts"
+
+ - id: "surface.workflow-run-detail-pane"
+ kind: "ui-surface"
+ label: "Workflow run side-pane tab (live causality graph, budget, cancel, event log)"
+ aliases:
+ - "workflow-run tab"
+ - "workflow run detail"
+ - "live workflow graph"
+ - "工作流运行详情"
+ - "工作流实时图"
+ docs:
+ - "docs/dynamic-workflow/presentation.md"
+ codeSeeds:
+ - symbol: "WorkflowCausalityGraph"
+ file: "packages/ui/src/components/workflow-graph/workflow-causality-graph.tsx"
+ - symbol: "WorkflowStepFlowNode"
+ file: "packages/ui/src/components/workflow-graph/step-node.tsx"
+ - symbol: "WorkspaceSidePaneTab"
+ file: "packages/ui/src/lib/workspaceSidePane.ts"
+
+ - id: "surface.workflow-actor-transcript"
+ kind: "ui-surface"
+ label: "Workflow actor transcript drill-down (read-only nested SessionPane)"
+ aliases:
+ - "actor transcript tab"
+ - "workflow agent session"
+ - "工作流 actor 会话"
+ - "工作流智能体 transcript"
+ docs:
+ - "docs/dynamic-workflow/presentation.md"
+ - "docs/subagent-session-side-tabs.md"
+ codeSeeds:
+ - symbol: "SubagentSessionSidePane"
+ file: "packages/ui/src/app-shell/SubagentSessionSidePane.tsx"
+ - symbol: "SessionPane"
+ file: "packages/ui/src/v4/SessionPane.tsx"
+
+ - id: "surface.workflow-tool-card-run-state"
+ kind: "ui-surface"
+ label: "CreateWorkflow tool card run state (compact clickable card once a run is joined)"
+ aliases:
+ - "workflow tool card"
+ - "workflow started card"
+ - "工作流工具卡"
+ - "工作流卡片运行态"
+ docs:
+ - "docs/dynamic-workflow/presentation.md"
+ codeSeeds:
+ - symbol: "CreateWorkflowToolCallBlock"
+ file: "packages/ui/src/ToolCallBlocks/renderers/create-workflow.tsx"
+ - symbol: "workflowRunByToolCallId"
+ file: "packages/ui/src/v4/SessionPane.tsx"
+
+ - id: "surface.workflow-task-island"
+ kind: "ui-surface"
+ label: "Workflows section in the conversation status capsule (Task Island) showing running workflow runs"
+ aliases:
+ - "task island"
+ - "workflow island"
+ - "workflow running pill"
+ - "任务岛"
+ - "工作流浮岛"
+ - "工作流分区"
+ docs:
+ - "docs/dynamic-workflow/presentation.md"
+ codeSeeds:
+ - symbol: "WorkflowStatusSection"
+ file: "packages/ui/src/v4/ConversationStatusPanel.tsx"
+ - symbol: "buildRunningWorkflowRuns"
+ file: "packages/ui/src/v4/conversationStatusPanelModel.ts"
+ - symbol: "getComposerBackgroundWorkCounts"
+ file: "packages/ui/src/v4/composer/ConversationBackgroundWorkTrigger.tsx"
+
+ - id: "capability.workflow-run-introspection"
+ kind: "capability"
+ label: "Workflow run introspection tools: ListWorkflowRuns / GetWorkflowRun read-only model tools over the dwf journal"
+ aliases:
+ - "list workflow runs"
+ - "workflow run status tool"
+ - "ListWorkflowRuns"
+ - "GetWorkflowRun"
+ - "工作流运行列表"
+ - "工作流状态查询"
+ - "工作流内省工具"
+ docs:
+ - "docs/dynamic-workflow/launch.md"
+ codeSeeds:
+ - symbol: "listWorkflowRunsToolEntry"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/handlers/list-workflow-runs.ts"
+ - symbol: "getWorkflowRunToolEntry"
+ file: "apps/ccbuddy-cli/packages/core/src/tool/handlers/get-workflow-run.ts"
+ - symbol: "createDwfJournalStore"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/session-store/repositories/dwf-journal.ts"
+
+ - id: "surface.submit-result-tool-card"
+ kind: "ui-surface"
+ label: "submit_result tool card (已提交结果): dedicated renderer for workflow actors' structured result submissions"
+ aliases:
+ - "submit result card"
+ - "submit_result renderer"
+ - "已提交结果"
+ - "提交结果工具卡"
+ docs:
+ - "docs/chat/submit-result-tool-card.md"
+ codeSeeds:
+ - symbol: "SubmitResultToolCallBlock"
+ file: "packages/ui/src/ToolCallBlocks/renderers/submit-result.tsx"
+ - symbol: "CCBUDDY_KNOWN_TOOL_NAMES"
+ file: "packages/shared/src/tool-identity.ts"
+ - symbol: "SUBMIT_RESULT_TOOL_NAME"
+ file: "apps/ccbuddy-cli/packages/contracts/src/tools/submit-result.ts"
+
+ - id: "state.workflow-run-journal"
+ kind: "state-owner"
+ label: "Dynamic workflow run journal and cancel registry (engine-owned run truth)"
+ docs:
+ - "apps/ccbuddy-cli/packages/dynamic-workflow/docs/execution-engine.md"
+ codeSeeds:
+ - symbol: "JournalStorePort"
+ file: "apps/ccbuddy-cli/packages/dynamic-workflow/src/engine/types.ts"
+ - symbol: "SqliteDwfJournalStore"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/session-store/repositories/dwf-journal.ts"
+
+ - id: "capability.conversation-sharing"
+ kind: "capability"
+ label: "Conversation sharing and shared-context continuation"
+ aliases:
+ - "share conversation"
+ - "conversation share"
+ - "会话分享"
+ - "回到 CCbuddy 继续"
+ docs:
+ - "docs/conversation-share-v1.md"
+ codeSeeds:
+ - symbol: "ConversationShareService"
+ file: "packages/services/src/conversation-share/conversationShareService.ts"
+ - symbol: "ConversationShareMenu"
+ file: "packages/ui/src/ConversationShareMenu.tsx"
+ - symbol: "formatSharedContextV1"
+ file: "packages/services/src/conversation-share/sharedContextFormatter.ts"
+
+ - id: "surface.conversation-share-publish"
+ kind: "ui-surface"
+ label: "Desktop conversation share selection and publish"
+ codeSeeds:
+ - symbol: "ConversationShareMenu"
+ file: "packages/ui/src/ConversationShareMenu.tsx"
+ - symbol: "ConversationShareConfirmationDock"
+ file: "packages/ui/src/v4/ConversationShareConfirmationDock.tsx"
+
+ - id: "service.conversation-share"
+ kind: "service"
+ label: "Conversation share API, remote artifact staging, and local import"
+ codeSeeds:
+ - symbol: "IConversationShareService"
+ file: "packages/services/src/conversation-share/conversationShare.ts"
+ - symbol: "ConversationShareHttpClient"
+ file: "packages/services/src/conversation-share/conversationShareHttpClient.ts"
+
+ - id: "surface.conversation-share-handover"
+ kind: "ui-surface"
+ label: "Desktop Share URL handover task"
+ docs:
+ - "docs/working-memory/provider-refactor/steps/todo-139-share-import-new-task-selection.md"
+ invariants:
+ - "Only reused=false import results seed an independent Composer draft before activation, keyed by the returned workspace identity. Copy initialized Root selection only; otherwise use the ordinary new-task Recent/default initializer after the View is ready. Empty history is not an import marker; user edits and reused sessions win. Import never updates Recent or waits for model execution."
+ codeSeeds:
+ - symbol: "seedImportedSessionDraft"
+ file: "packages/ui/src/v4/composer/newTaskDraft.ts"
+ - symbol: "useDraftConfigControl"
+ file: "packages/ui/src/v4/composer/useDraftConfigControl.ts"
+ - symbol: "ConversationShareLandingPage"
+ file: "packages/web/src/share/ConversationShareLandingPage.tsx"
+ - symbol: "useRootPlatformEffects"
+ file: "packages/ui/src/root/useRootPlatformEffects.ts"
+ - symbol: "ConversationShareImportNotice"
+ file: "packages/ui/src/v4/ConversationShareImportNotice.tsx"
+
+ - id: "shared-ui.conversation-share-context-chip"
+ kind: "shared-ui"
+ label: "Share URL context chip and parser"
+ codeSeeds:
+ - symbol: "serializeConversationShareContext"
+ file: "packages/ui/src/lib/conversationShareContext.ts"
+ - symbol: "parseConversationShareContext"
+ file: "packages/ui/src/lib/conversationShareContext.ts"
+
+ - id: "persistence.conversation-share-context"
+ kind: "persistence"
+ label: "Shared context import status and provenance"
+ codeSeeds:
+ - symbol: "transitionSharedContextImport"
+ file: "apps/ccbuddy-cli/packages/adapters/src/storage/session-store/sqlite-session-store.ts"
+
+ - id: "service.ccbuddy-agent-connection-scope"
+ kind: "service"
+ label: "Host-trusted CCbuddy Agent attachment connection scope"
+ codeSeeds:
+ - symbol: "createCCbuddyAgentConnectionScope"
+ file: "packages/services/src/ccbuddy-agent/ccbuddyAgentConnectionScope.ts"
+
+ - id: "capability.plugin-creator-entry"
+ kind: "capability"
+ label: "Localized plugin management and bundled Plugin Creator draft entry"
+ aliases:
+ ["plugin-creator", "创建插件", "插件本地化", "添加插件市场", "拼音搜索", "插件默认展开"]
+ docs: ["docs/plugin-creator.md", "docs/plugin-capabilities-settings.md"]
+ codeSeeds:
+ - file: "packages/ui/src/settings/PluginAddMenu.tsx"
+ - file: "packages/ui/src/hooks/usePluginCreator.ts"
+ - file: "packages/ui/src/settings/pluginStoreListing.ts"
+ - file: "packages/ui/src/settings/pluginSearch.ts"
+ - file: "packages/ui/src/settings/PluginStoreListView.tsx"
+ - file: "packages/ui/src/settings/pluginCapabilityProjection.ts"
+ - file: "apps/ccbuddy-cli/packages/bootstrap/src/app/official-plugin-definitions.ts"
+ invariants:
+ - "Creator only prefills an unsent draft using the target host official skill path."
+ - "Market and User management share the two-item menu; Workspace config adds no marketplace mutations."
+ - "Localization joins by complete pluginId; identity and runtime payloads do not change."
+ - "Market groups show all cards by default; manual collapse stays local to the current page."
+
+ - id: "capability.bot-group-collaboration"
+ kind: "capability"
+ label: "Bot group and topic collaboration"
+ aliases: ["群聊机器人", "话题续接", "ReplyToChannel", "渠道提及", "Bot 历史附件"]
+ docs:
+ [
+ "docs/bots-feishu-group-collaboration.md",
+ "docs/bots-topic-input-materials.md",
+ "docs/testing/bots-e2e-coverage-matrix.md",
+ ]
+ codeSeeds:
+ - file: "packages/services/src/bots/botsService.ts"
+ symbol: "createBotsService"
+ - file: "packages/services/src/bots/channelReply.ts"
+ symbol: "replyToBotChannel"
+ - file: "packages/services/src/bots/topicContinuation.ts"
+ symbol: "createTopicContinuation"
+ - id: "surface.bot-bound-groups"
+ kind: "ui-surface"
+ label: "Bot settings bound group controls"
+ codeSeeds:
+ - file: "packages/ui/src/BotsDialog/BoundGroupsCard.tsx"
+ symbol: "BoundGroupsCard"
+ - id: "surface.bot-channel-messages"
+ kind: "ui-surface"
+ label: "Canonical group input and native mentions"
+ codeSeeds:
+ - file: "packages/ui/src/v4/ChannelMentionContent.tsx"
+ symbol: "ChannelMentionContent"
+ - file: "packages/ui/src/v4/BotTopicPreparation.tsx"
+ symbol: "BotTopicPreparationList"
+
edges:
- - from: capability.model-selection
- to: surface.conversation-model-control
- type: renders-in
- rank: must-inspect
- - from: surface.conversation-model-control
- to: shared-ui.model-config-select
- type: shares-component
- rank: must-inspect
- - from: capability.model-selection
- to: surface.automation-edit
- type: renders-in
- rank: must-inspect
- - from: surface.automation-edit
- to: shared-ui.model-config-select
- type: shares-component
- rank: must-inspect
- - from: capability.model-selection
- to: surface.user-subagent-form
- type: renders-in
- rank: must-inspect
- - from: surface.user-subagent-form
- to: shared-ui.model-config-select
- type: shares-component
- rank: must-inspect
- - from: capability.model-selection
- to: surface.builtin-subagent-model
- type: renders-in
- rank: must-inspect
- - from: surface.builtin-subagent-model
- to: shared-ui.model-config-select
- type: shares-component
- rank: must-inspect
- - from: surface.conversation-model-control
- to: shared-ui.model-select-groups
- type: options-from
- rank: should-inspect
- - from: surface.user-subagent-form
- to: shared-ui.model-select-groups
- type: options-from
- rank: should-inspect
- - from: surface.builtin-subagent-model
- to: shared-ui.model-select-groups
- type: options-from
- rank: should-inspect
- - from: capability.model-selection
- to: service.host-model-selection
- type: reads-candidates-from
- rank: must-inspect
- - from: service.host-model-selection
- to: state.renderer-model-selection-read
- type: is-read-through
- rank: must-inspect
- - from: state.renderer-model-selection-read
- to: boundary.workspace-key
- type: isolated-by
- rank: must-inspect
- condition:
- Read the selected workspace service; remote-waiting remains unavailable and must not use
- a local fallback.
+ - from: "capability.permission-full-access"
+ to: "surface.permission-full-access"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "Only explicit fullAccessOption capability; preserve each Plan, project defaults and ordinary Composer menu isolation."
+ - from: "capability.plugin-creator-entry"
+ to: "surface.workspace-plugin-scope-controls"
+ type: "exposed-through"
+ rank: "must-inspect"
+ - from: "capability.plugin-creator-entry"
+ to: "surface.plugin-store-example-prompt-try"
+ type: "shares-new-task-prefill"
+ rank: "must-inspect"
+ - from: "capability.plugin-creator-entry"
+ to: "capability.workspace-identity"
+ type: "isolated-by"
+ rank: "must-inspect"
+ - from: "capability.plugin-creator-entry"
+ to: "boundary.plugin-agent-host"
+ type: "resolves-skill-from-target"
+ rank: "must-inspect"
+ - from: "capability.plugin-store-mode-order"
+ to: "state.interface-mode"
+ type: "selects-order-from"
+ rank: "must-inspect"
+ - from: "capability.marketing-touch"
+ to: "capability.referrals-embedded"
+ type: "navigates-through-window-provider"
+ rank: "must-inspect"
+ condition: "navigate page=rewards preserves login continuation and single confirm reporting."
+ - from: "capability.interface-mode"
+ to: "state.interface-mode"
+ type: "configured-by"
+ rank: "must-inspect"
+ - from: "state.interface-mode"
+ to: "surface.interface-mode-panels"
+ type: "filters-creation-without-closing-existing"
+ rank: "must-inspect"
+ - from: "state.interface-mode"
+ to: "state.conversation-projection"
+ type: "filters-rendering-without-mutating"
+ rank: "must-inspect"
+ - from: "state.interface-mode"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "state.interface-mode"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "capability.dangerous-command-approval"
+ to: "surface.parent-task-blocking-interaction"
+ type: "requires-direct-approval-in"
+ rank: "must-inspect"
+ - from: "capability.dangerous-command-approval"
+ to: "boundary.desktop-continuous"
+ type: "preserves-delivery-boundary"
+ rank: "invariant-only"
+ - from: "capability.dangerous-command-approval"
+ to: "boundary.web-remote-replayable"
+ type: "preserves-delivery-boundary"
+ rank: "invariant-only"
+ - from: "capability.dangerous-command-approval"
+ to: "boundary.workspace-key"
+ type: "preserves-identity-isolation"
+ rank: "invariant-only"
+ - from: "capability.referrals-embedded"
+ to: "state.coding-plan-purchase-funnel"
+ type: "shares-presentation-only"
+ rank: "should-inspect"
+ condition: "Shared navigation header must preserve Coding Plan callbacks and purchase behavior."
+ - from: "capability.referrals-embedded"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "capability.marketing-touch"
+ to: "state.marketing-touch-controller"
+ type: "owned-by"
+ rank: "must-inspect"
+ - from: "state.marketing-touch-controller"
+ to: "service.marketing-touch"
+ type: "queries-and-reports-through"
+ rank: "must-inspect"
+ - from: "capability.marketing-touch"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "capability.marketing-touch"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "capability.delete-all-archived-tasks"
+ to: "surface.desktop-archived-tasks"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.delete-all-archived-tasks"
+ to: "surface.web-archived-tasks"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.delete-all-archived-tasks"
+ to: "capability.task-persistence"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "surface.message-stream-reasoning-setting"
+ to: "persistence.app-settings"
+ type: "persists-to"
+ rank: "must-inspect"
+ - from: "persistence.app-settings"
+ to: "state.conversation-reasoning-visibility"
+ type: "projects-renderer-preference-to"
+ rank: "must-inspect"
+ condition: "A missing migration marker forces true exactly once; after the marker is persisted, the stored boolean is authoritative."
+ - from: "state.conversation-reasoning-visibility"
+ to: "state.conversation-projection"
+ type: "filters-rendering-without-mutating"
+ rank: "must-inspect"
+ condition: "The resolved preference hides only later reasoning rows when false; the first reasoning row stays visible and canonical projection is unchanged."
+ - from: "state.conversation-reasoning-visibility"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "state.conversation-reasoning-visibility"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "capability.hooks-settings"
+ to: "surface.hooks-settings"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.hooks-settings"
+ to: "state.hooks-settings-store"
+ type: "reads-and-commits-through"
+ rank: "must-inspect"
+ - from: "state.hooks-settings-store"
+ to: "persistence.hooks-config-files"
+ type: "persists-to"
+ rank: "must-inspect"
+ condition: "workspaceIdentity?.trim() || workspacePath isolates state; workspacePath selects the file target."
+ - from: "capability.hooks-settings"
+ to: "capability.agent-core-runtime"
+ type: "must-preserve-runtime-contract"
+ rank: "invariant-only"
+ condition: "Workspace hooks remain editable but are excluded from runtime; enabled plugin hooks remain runtime-owned snapshots."
+
+ - from: "capability.customize-capability-search"
+ to: "surface.customize-capability-settings"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.customize-capability-settings"
+ to: "state.customize-tab-search-query"
+ type: "owns-draft"
+ rank: "must-inspect"
+ condition: "Each capability tab retains its own query; scope changes filter the newly selected isolated scope without sharing resources across workspace identities."
+ - from: "capability.customize-capability-search"
+ to: "evidence.customize-capability-settings-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.command-workspace-scope"
+ to: "surface.command-settings-editor"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.command-settings-editor"
+ to: "shared-ui.customize-scope-menu"
+ type: "shares-component"
+ rank: "must-inspect"
+ - from: "surface.command-settings-editor"
+ to: "state.command-form-scope-key"
+ type: "owns-draft"
+ rank: "must-inspect"
+ condition: "A new form defaults to User; editing retains a locked owner scope and never migrates storage."
+ - from: "state.command-form-scope-key"
+ to: "boundary.workspace-key"
+ type: "isolated-by"
+ rank: "must-inspect"
+ condition: "Workspace options use workspaceIdentity?.trim() || workspacePath; file IO continues to use workspacePath."
+ - from: "surface.command-settings-editor"
+ to: "service.workspace-commands"
+ type: "commits-to"
+ rank: "must-inspect"
+ condition: "A concrete Workspace selection resolves that tab's local or remote Host service before writing."
+ - from: "service.workspace-commands"
+ to: "persistence.command-settings-directories"
+ type: "persists-to"
+ rank: "must-inspect"
+ - from: "capability.command-workspace-scope"
+ to: "surface.customize-capability-settings"
+ type: "shares-scope-option-contract-with"
+ rank: "should-inspect"
+ condition: "Commands reuse the concrete User and open-Workspace option contract without joining Plugin list state."
+ - from: "capability.command-workspace-scope"
+ to: "evidence.command-workspace-scope-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.browser-use-plugin-distribution"
+ to: "capability.build-release-engineering"
+ type: "packaged-and-versioned-by"
+ rank: "must-inspect"
+ - from: "capability.browser-use-plugin-distribution"
+ to: "capability.agent-core-runtime"
+ type: "seeds-runtime-assets-for"
+ rank: "must-inspect"
+ - from: "capability.browser-use-plugin-distribution"
+ to: "evidence.browser-use-plugin-distribution-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.browser-use-plugin-distribution"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.browser-use-plugin-distribution"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+
+ - from: "capability.browser-use-agent-guidance"
+ to: "capability.agent-core-runtime"
+ type: "guides-tool-planning-in"
+ rank: "must-inspect"
+ - from: "capability.browser-use-agent-guidance"
+ to: "evidence.browser-use-agent-guidance-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.browser-use-agent-guidance"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.browser-use-agent-guidance"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+
+ - from: "capability.windows-window-chrome"
+ to: "surface.workspace-shell-window-edge"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.workspace-shell-window-edge"
+ to: "state.desktop-window-chrome"
+ type: "projects-from"
+ rank: "must-inspect"
+ - from: "capability.windows-window-chrome"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.assistant-file-preview-links"
+ to: "surface.conversation-assistant-file-preview"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.conversation-assistant-file-preview"
+ to: "state.assistant-file-preview-projection"
+ type: "projects-from"
+ rank: "must-inspect"
+ condition: "Cards derive from terminal assistant text plus current-turn fileChanges; SessionPane shares a bounded in-flight/result query cache above virtualized rows, and terminal cache identity includes the authoritative active/reverted fileChanges state so rewind cannot reuse pre-rewind results. Assistant body text keeps the existing Streamdown behavior and does not auto-link plain file-name mentions."
+ - from: "state.assistant-file-preview-projection"
+ to: "service.workspace-file-existence-check"
+ type: "validates-through"
+ rank: "must-inspect"
+ condition: "Workspace-relative references are lexically normalized and rejected if they escape the workspace before Host I/O; only the first 15 reverse-occurrence candidates are checked, and at most 10 valid cards render."
+ - from: "service.workspace-file-existence-check"
+ to: "boundary.workspace-key"
+ type: "isolated-by"
+ rank: "must-inspect"
+ condition: "useWorkspaceServices selects the local or remote Host file service; workspacePath remains the file execution path."
+ - from: "surface.conversation-assistant-file-preview"
+ to: "surface.preview-pane-office-file"
+ type: "opens-in"
+ rank: "must-inspect"
+ - from: "state.assistant-file-preview-projection"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "state.assistant-file-preview-projection"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "capability.workspace-file-tree"
+ to: "surface.workspace-file-tree-panel"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.workspace-file-tree-panel"
+ to: "state.workspace-file-tree-projection"
+ type: "projects-from"
+ rank: "must-inspect"
+ - from: "capability.workspace-file-tree"
+ to: "evidence.workspace-file-tree-e2e"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.office-file-preview"
+ to: "surface.preview-pane-office-file"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.preview-pane-office-file"
+ to: "state.preview-pane-office-render"
+ type: "owns-local-render-lifecycle"
+ rank: "must-inspect"
+ condition: "DOCX rendering is local to the active Preview Pane and stale asynchronous results must not replace a newer source."
+ - from: "surface.preview-pane-office-file"
+ to: "service.workspace-file-binary-preview"
+ type: "reads-through"
+ rank: "must-inspect"
+ - from: "service.workspace-file-binary-preview"
+ to: "boundary.workspace-key"
+ type: "isolated-by"
+ rank: "must-inspect"
+ condition: "The selected workspace service owns file reads; workspacePath remains the execution path."
+ - from: "state.preview-pane-office-render"
+ to: "boundary.desktop-continuous"
+ type: "must-remain-isolated-from"
+ rank: "invariant-only"
+ - from: "state.preview-pane-office-render"
+ to: "boundary.web-remote-replayable"
+ type: "must-remain-isolated-from"
+ rank: "invariant-only"
+ - from: "capability.side-pane-tab-strip"
+ to: "surface.workspace-side-pane-tab-strip"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.workspace-side-pane-tab-strip"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "surface.workspace-side-pane-tab-strip"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery"
+ rank: "invariant-only"
+
+ - from: "capability.markdown-image-gallery"
+ to: "capability.shared-image-preview"
+ type: "shares-component"
+ rank: "must-inspect"
+ - from: "capability.shared-image-preview"
+ to: "capability.conversation-runtime"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "Markdown and Browser Use / node_repl images render inside conversation rows."
+ - from: "capability.shared-image-preview"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.shared-image-preview"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.markdown-image-gallery"
+ to: "capability.conversation-runtime"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "Assistant conversation rows render Markdown through the shared MessageResponse component."
+ - from: "capability.markdown-image-gallery"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.markdown-image-gallery"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+
+ - from: "capability.embedded-browser-network-policy"
+ to: "surface.desktop-network-policy-setting"
+ type: "configured-in"
+ rank: "must-inspect"
+ - from: "surface.desktop-network-policy-setting"
+ to: "persistence.app-settings"
+ type: "persists-explicit-network-policy-to"
+ rank: "must-inspect"
+ - from: "capability.embedded-browser-network-policy"
+ to: "service.desktop-chromium-network-policy"
+ type: "implemented-by"
+ rank: "must-inspect"
+ - from: "service.desktop-chromium-network-policy"
+ to: "surface.browser-use-side-pane-guest"
+ type: "configures-electron-session-for"
+ rank: "must-inspect"
+ condition: "Desktop startup applies the explicit proxy, No Proxy, and custom CA policy independently to defaultSession and persist:ccbuddy-embedded-browser; persist:ccbuddy-coding-plan and other dedicated partitions remain isolated."
+ - from: "service.desktop-chromium-network-policy"
+ to: "boundary.desktop-continuous"
+ type: "owned-locally-by"
+ rank: "must-inspect"
+ - from: service.desktop-chromium-network-policy
+ to: boundary.web-remote-replayable
+ type: must-not-add-recovery-state-to
+ rank: invariant-only
+ condition: The Electron Session policy affects the existing shared desktop guest only; it adds no relay, task snapshot, or Web/SSH replayable state.
+ - from: "capability.embedded-browser-network-policy"
+ to: "evidence.embedded-browser-network-policy-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.host-api-network-policy"
+ to: "surface.desktop-network-policy-setting"
+ type: "configured-in"
+ rank: "must-inspect"
+ condition: "AppSettings httpProxy, httpProxyNoProxy, and httpProxyCaCertPath remain the only explicit desktop authority and take effect for Host API egress on the next Window Host lifecycle."
+ - from: "surface.desktop-network-policy-setting"
+ to: "service.host-api-network-policy"
+ type: "supplies-lifecycle-snapshot-to"
+ rank: "must-inspect"
+ - from: "capability.host-api-network-policy"
+ to: "service.host-api-network-policy"
+ type: "implemented-by"
+ rank: "must-inspect"
+ - from: "service.host-api-network-policy"
+ to: "service.oauth"
+ type: "injects-node-api-client-transport-for"
+ rank: "must-inspect"
+ condition: "OAuth token and user-info requests use the final rewritten URL for No Proxy selection and fail closed when an explicit proxy or custom CA is invalid."
+ - from: "service.host-api-network-policy"
+ to: "service.model-provider"
+ type: "injects-node-api-client-transport-for"
+ rank: "must-inspect"
+ condition: "Preset provider refresh, endpoint configuration, billing, and connectivity requests that already use NodeApiClient follow the same Host lifecycle policy; Agent model transport remains separate."
+ - from: "service.host-api-network-policy"
+ to: "service.plan-entitlements"
+ type: "injects-node-api-client-transport-for"
+ rank: "must-inspect"
+ condition: "Host-owned Coding Plan subscription and usage requests follow the explicit desktop policy without moving plan authority into relay, main, or remote Agent state."
+ - from: "service.host-api-network-policy"
+ to: "service.client-scenes"
+ type: "injects-node-api-client-transport-for"
+ rank: "must-inspect"
+ condition: "Client scenes keeps its current service/cache authority; only the Host HTTP transport changes."
+ - from: "service.host-api-network-policy"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.host-api-network-policy"
+ to: "evidence.host-api-network-policy-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.browser-screenshot-surface"
+ to: "surface.browser-use-side-pane-guest"
+ type: "composes-guest-in"
+ rank: "must-inspect"
+ - from: "surface.browser-use-side-pane-guest"
+ to: "state.browser-screenshot-surface-coordination"
+ type: "prepared-and-released-by"
+ rank: "must-inspect"
+ - from: "capability.browser-screenshot-surface"
+ to: "state.browser-screenshot-surface-coordination"
+ type: "coordinated-by"
+ rank: "must-inspect"
+ - from: "state.browser-screenshot-surface-coordination"
+ to: "service.desktop-browser-screenshot-activity"
+ type: "temporarily-wakes-owner-renderer-and-target-guest-through"
+ rank: "must-inspect"
+ condition: "Only the active screenshot preparation group holds a bounded, event-loop-fair activity lease for its owner renderer and target guest; each continuous pump turn yields a macrotask so IPC, timeout, cancellation, and lifecycle events remain schedulable, while sibling guests, queued groups, and persistent tabs remain background-throttled."
+ - from: "service.desktop-browser-screenshot-activity"
+ to: "boundary.desktop-continuous"
+ type: "owned-locally-by"
+ rank: "must-inspect"
+ - from: service.desktop-browser-screenshot-activity
+ to: boundary.web-remote-replayable
+ type: must-remain-isolated-from
+ rank: invariant-only
+ condition: Screenshot activity is transient Electron main state and never enters relay, task snapshots, or Web/SSH recovery.
+ - from: "capability.browser-screenshot-surface"
+ to: "boundary.workspace-key"
+ type: "preserves-existing-scope"
+ rank: "invariant-only"
+ - from: "capability.browser-screenshot-surface"
+ to: "evidence.browser-screenshot-surface-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.browser-webview-video-recording"
+ to: "surface.browser-use-side-pane-guest"
+ type: "records-guest-from"
+ rank: "must-inspect"
+ condition: "Recording captures the already-authorized IAB guest and runs its restricted action DSL through trusted CDP input; it never launches an independent Chromium runtime."
+ - from: "capability.browser-webview-video-recording"
+ to: "state.browser-recording-job-registry"
+ type: "owned-transiently-by"
+ rank: "must-inspect"
+ condition: "Jobs are scoped by browser generation, window, workspaceKey, remoteSessionId, session, clientMode and tab; recording ids are correlation handles, not authorization."
+ - from: "state.browser-recording-job-registry"
+ to: "state.browser-screenshot-surface-coordination"
+ type: "holds-bounded-capture-lease-through"
+ rank: "must-inspect"
+ condition: "Preparing and capturing hold the target guest's background surface so hidden side panes keep producing screencast frames; every terminal path releases the lease."
+ - from: "capability.browser-webview-video-recording"
+ to: "capability.stateless-browser-use-mcp"
+ type: "exposed-to-plugins-through"
+ rank: "must-inspect"
+ condition: "Third-party plugins call the official Tab recording facade from fresh node_repl kernels; no plugin manifest can claim raw CDP or Electron authority."
+ - from: "state.browser-recording-job-registry"
+ to: "boundary.workspace-key"
+ type: "isolated-by"
+ rank: "must-inspect"
+ condition: "Every status and cancel request rechecks workspaceIdentity?.trim() || workspacePath, remoteSessionId, session, browser generation, clientMode and tab ownership."
+ - from: "capability.browser-webview-video-recording"
+ to: "boundary.desktop-continuous"
+ type: "executes-locally-without-changing-delivery"
+ rank: "must-inspect"
+ - from: "capability.browser-webview-video-recording"
+ to: "evidence.browser-webview-video-recording-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.embedded-browser-responsive-viewport"
+ to: "surface.browser-use-side-pane-guest"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "Agent Browser Use reuses the viewport renderer but never reads or writes the human presentation preference."
+ - from: "capability.embedded-browser-responsive-viewport"
+ to: "surface.human-browser-side-pane-guest"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.human-browser-side-pane-guest"
+ to: "state.embedded-browser-human-viewport-preference"
+ type: "initializes-once-from-and-commits-human-actions-to"
+ rank: "must-inspect"
+ condition: "Each new human Browser tab consumes normal/responsive mode, last valid viewport, and preview zoom once; mounted tabs keep tab-local runtime state after setting broadcasts."
+ - from: "state.embedded-browser-human-viewport-preference"
+ to: "persistence.app-settings"
+ type: "persists-to"
+ rank: "must-inspect"
+ condition: "Only presentation preference is persisted; Browser tab shells, URLs, history, pageState, guest identity, and Agent viewport runtime are excluded."
+ - from: "state.embedded-browser-human-viewport-preference"
+ to: "surface.browser-use-side-pane-guest"
+ type: "must-remain-isolated-from"
+ rank: "must-inspect"
+ condition: "Agent-created tabs, claim, navigate, viewport set/reset, and renderer viewport events neither inherit nor mutate the human preference; Agent creation remains 1280x720 Fit."
+ - from: "state.embedded-browser-human-viewport-preference"
+ to: "boundary.desktop-continuous"
+ type: "owned-as-app-setting-outside-task-delivery"
+ rank: "must-inspect"
+ - from: state.embedded-browser-human-viewport-preference
+ to: boundary.web-remote-replayable
+ type: must-remain-isolated-from
+ rank: invariant-only
+ condition: The preference never enters task stream, snapshot, replay, relay, owner queue, or Web/SSH recovery state.
+ - from: "surface.browser-use-side-pane-guest"
+ to: "state.embedded-browser-resize-gesture"
+ type: "owns-local-pointer-lifecycle"
+ rank: "must-inspect"
+ condition: "Pointer up, cancel, lost capture, window blur, mode exit, and unmount must end the renderer-local drag; refocus never resumes it without a new pointerdown."
+ - from: "state.embedded-browser-resize-gesture"
+ to: "boundary.desktop-continuous"
+ type: "owned-locally-by"
+ rank: "must-inspect"
+ - from: state.embedded-browser-resize-gesture
+ to: boundary.web-remote-replayable
+ type: must-remain-isolated-from
+ rank: invariant-only
+ condition: The pointer gesture is renderer-local UI state and never enters task snapshots, replay, relay, or Web/SSH recovery.
+ - from: "surface.browser-use-side-pane-guest"
+ to: "state.embedded-browser-fit-presentation"
+ type: "owns-local-fit-presentation"
+ rank: "must-inspect"
+ condition: "Only positive composed canvas measurements are authoritative; display:none 0x0 samples preserve the last valid Fit scale, and a newly composed tab is remeasured before its first visible paint."
+ - from: "state.embedded-browser-fit-presentation"
+ to: "boundary.desktop-continuous"
+ type: "owned-locally-by"
+ rank: "must-inspect"
+ - from: state.embedded-browser-fit-presentation
+ to: boundary.web-remote-replayable
+ type: must-remain-isolated-from
+ rank: invariant-only
+ condition: Fit canvas measurement is renderer-local presentation state and never enters task snapshots, replay, relay, Web/SSH recovery, guest viewport commands, or tab ownership.
+ - from: "state.embedded-browser-fit-presentation"
+ to: "evidence.embedded-browser-responsive-viewport-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.embedded-browser-responsive-viewport"
+ to: "evidence.embedded-browser-responsive-viewport-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.browser-tab-residency"
+ to: "surface.browser-tab-shell"
+ type: "renders-logical-tabs-in"
+ rank: "must-inspect"
- from: capability.automations
- to: surface.automation-edit
- type: renders-in
- rank: must-inspect
- - from: surface.automation-edit
- to: state.automation-form-draft
- type: owns-draft
- rank: must-inspect
- - from: surface.automation-edit
- to: state.renderer-model-selection-read
- type: reads-target-options-through
- rank: should-inspect
- - from: state.automation-form-draft
- to: service.automation
- type: commits-to
- rank: must-inspect
- condition:
- Trace onSubmit through AutomationsSection and the automation management store to the selected
- service; editing a field alone does not mutate a conversation.
- - from: service.automation
- to: persistence.automation-record
- type: persists-to
- rank: must-inspect
- - from: capability.subagents
- to: surface.user-subagent-form
- type: renders-in
- rank: must-inspect
- - from: capability.subagents
- to: surface.builtin-subagent-model
- type: renders-in
- rank: must-inspect
- - from: surface.user-subagent-form
- to: state.subagent-form-draft
- type: owns-draft
- rank: must-inspect
- - from: state.subagent-form-draft
- to: service.subagents
- type: commits-to
- rank: must-inspect
- condition: The form saves through createAgent or updateAgent.
- - from: surface.builtin-subagent-model
- to: service.subagents
- type: commits-to
- rank: must-inspect
- condition: Built-in and plugin overrides use setBuiltInModelOverride and setPluginAgentModelOverride.
- - from: service.subagents
- to: persistence.subagent-markdown
- type: persists-to
+ to: surface.browser-use-side-pane-guest
+ type: may-trigger-background-guest-while
rank: must-inspect
- condition:
- User/workspace definitions use Markdown; built-in/plugin overrides use the agent state
- file. Neither is a live conversation model switch.
- - from: capability.conversation-runtime
- to: service.conversation-command-inbox
- type: admits-commands-through
- rank: must-inspect
- - from: capability.conversation-runtime
- to: state.conversation-product-projection
- type: projects-runtime-events-through
- rank: must-inspect
- - from: state.conversation-product-projection
- to: service.conversation-topic-publisher
- type: publishes-deltas-through
+ condition: Immediate and scheduled automation runs may create Browser Use tabs while the Automations main view remains visible; the Desktop Side Pane host stays mounted but collapsed, completes guest attach without stealing focus, and adds no Web/SSH replayable state or second BrowserWindow.
+ - from: "surface.browser-tab-shell"
+ to: "state.browser-tab-residency"
+ type: "projects-and-reports"
+ rank: "must-inspect"
+ - from: "state.browser-tab-residency"
+ to: "service.desktop-browser-tab-residency"
+ type: "scheduled-by"
+ rank: "must-inspect"
+ condition: "Each BrowserWindow keeps at most 32 process-local logical Browser/Browser Use tabs; the oldest safe candidate is fully closed, and complete app restart begins with an empty tab catalog."
+ - from: "service.desktop-browser-tab-residency"
+ to: "persistence.browser-tab-recovery-store"
+ type: "must-remain-disconnected-from"
+ rank: "invariant-only"
+ condition: "Desktop main no longer injects, reads, or appends the legacy recovery repository; Browser tabs are never restored across complete app restart."
+ - from: "service.desktop-browser-tab-residency"
+ to: "capability.desktop-app-shutdown"
+ type: "ends-all-tabs-with"
+ rank: "must-inspect"
+ condition: "Normal quit, update-install quit, and the next packaged or development process do not persist or restore Browser tab shells, URLs, history, or pageState."
+ - from: "service.desktop-browser-tab-residency"
+ to: "service.desktop-browser-screenshot-activity"
+ type: "honors-protection-lease-from"
+ rank: "must-inspect"
+ condition: "A Browser screenshot/capture target cannot be automatically closed until the bounded activity and screenshot surface leases truly release."
+ - from: "service.desktop-browser-tab-residency"
+ to: "boundary.desktop-continuous"
+ type: "owned-locally-by"
+ rank: "must-inspect"
+ - from: service.desktop-browser-tab-residency
+ to: boundary.web-remote-replayable
+ type: must-remain-isolated-from
+ rank: invariant-only
+ condition: Residency, pageState, guest generations, and renderer budgets remain Electron-local and never enter relay or Web/SSH task snapshots.
+ - from: "service.desktop-browser-tab-residency"
+ to: "boundary.workspace-key"
+ type: "isolates-logical-tabs-and-snapshots-by"
+ rank: "must-inspect"
+ condition: "Remote scopes additionally preserve remoteSessionId; physical BrowserWindow budget sharing never permits logical state sharing."
+ - from: "capability.browser-tab-residency"
+ to: "evidence.browser-tab-residency-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.browser-tab-residency"
+ to: "boundary.local-development-runtime"
+ type: "clears-on-process-restart-in"
+ rank: "must-inspect"
+ - from: "capability.browser-tab-residency"
+ to: "boundary.packaged-desktop-runtime"
+ type: "clears-on-process-restart-in"
+ rank: "must-inspect"
+
+ - from: "capability.provider-endpoint-routing"
+ to: "service.provider-endpoint-routing"
+ type: "implemented-by"
+ rank: "must-inspect"
+ - from: "service.provider-endpoint-routing"
+ to: "state.provider-endpoint-routing-snapshot"
+ type: "owns-lazy-cache"
+ rank: "must-inspect"
+ condition: "The registry shares a port by endpoint and network policy. Dynamic implementations refresh stale snapshots with coalesced GETs; static implementations do no configuration IO. Neither persists routing state."
+ - from: "service.provider-endpoint-routing"
+ to: "boundary.ccbuddy-cli-provider-transport"
+ type: "rewrites-endpoint-for"
+ rank: "must-inspect"
+ condition: "Only default AI SDK provider fetches are rewritten before proxy/no-proxy resolution."
+ - from: "boundary.ccbuddy-cli-provider-transport"
+ to: "service.provider-endpoint-routing"
+ type: "supplies-x-api-key-id-on-stale-refresh"
+ rank: "must-inspect"
+ condition: "For dynamic implementations, only account requestAuth.apiKeyId may cross into a stale agent/configs GET as x-api-key-id; manual keys, PAT, Authorization and model headers remain isolated. Static implementations do not request configuration. The ID is not logged or persisted by routing."
+ - from: "capability.provider-registry"
+ to: "boundary.ccbuddy-cli-provider-transport"
+ type: "creates"
+ rank: "must-inspect"
+ - from: "boundary.ccbuddy-cli-provider-transport"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "boundary.ccbuddy-cli-provider-transport"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+
+ - from: "capability.agent-core-runtime"
+ to: "capability.conversation-runtime"
+ type: "executes-authoritative-turns-for"
+ rank: "must-inspect"
+ condition: "Conversation commands, queue state, and projection are owned by the CLI runtime rather than Renderer or Host."
+ - from: "capability.agent-core-runtime"
+ to: "capability.model-capabilities"
+ type: "consumes-runtime-capabilities-from"
+ rank: "must-inspect"
+
+ - from: "capability.read-pdf"
+ to: "capability.model-capabilities"
+ type: "conditions-tool-shape-and-media-branch-on"
+ rank: "must-inspect"
+ condition: "supportsPdf controls native PDF/schema exposure; supportsImages independently gates pages rendering."
+ - from: "capability.read-pdf"
+ to: "capability.agent-core-runtime"
+ type: "executes-through"
+ rank: "must-inspect"
+ condition: "Main, subagent, project-memory and workflow runtimes share the injected PdfDocumentPort."
+ - from: "capability.read-pdf"
+ to: "boundary.ccbuddy-cli-provider-transport"
+ type: "projects-ordered-tool-media-through"
+ rank: "must-inspect"
+ condition: "Anthropic and Responses keep structured tool media; Chat keeps summary in tool output and ordered images in one following user message."
+ - from: "capability.read-pdf"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "capability.read-pdf"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+
+ - from: "capability.plan-entitlements"
+ to: "service.plan-entitlements"
+ type: "implemented-by"
+ rank: "must-inspect"
+ condition: "Personal Coding Plan, Team Plan, and Start Plan retain distinct identity, credential, quota, and billing semantics."
+ - from: "service.plan-entitlements"
+ to: "surface.plan-usage"
+ type: "projects-remote-quota-to"
+ rank: "must-inspect"
+ - from: "capability.plan-entitlements"
+ to: "capability.provider-registry"
+ type: "gates-provider-availability"
+ rank: "must-inspect"
+ condition: "Plan facts affect Built-in account Provider availability only through Account Config and the formal Provider Resolver. Renderer plan-card status does not reapply Account Overlay or gate Effective Provider and Model visibility in Provider Settings."
+ - from: "capability.plan-entitlements"
+ to: "surface.provider-settings"
+ type: "renders-status-card-without-owning-provider-configuration"
+ rank: "must-inspect"
+ condition: "Team Plan selection scopes entitlement and quota requests, while ProviderSettingsView independently supplies the Effective Provider and Model editor. Loading, request failure, credential unavailability, or explicit non-allocation never hides an existing Provider configuration."
+
+ - from: "capability.plan-entitlements"
+ to: "surface.start-plan-manual-claim"
+ type: "renders-claimable-preview-in"
+ rank: "must-inspect"
+ condition: "Preview is exposure-only; claim requires authentication and captcha, and success remains authoritative across best-effort refresh failures."
+ - from: "surface.start-plan-manual-claim"
+ to: "service.plan-entitlements"
+ type: "claims-and-refreshes-through"
+ rank: "must-inspect"
+ - from: "surface.start-plan-manual-claim"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-create-recovery-state-in"
+ rank: "invariant-only"
+ - from: "surface.start-plan-manual-claim"
+ to: "evidence.start-plan-manual-claim-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.cloud-content-dialog"
+ to: "shared.cloud-dialog-hero"
+ type: "implemented-by"
+ rank: "must-inspect"
+ - from: "service.cloud-content-assets"
+ to: "capability.desktop-app-shutdown"
+ type: "must-finish-async-resource-release-before-host-exit"
+ rank: "must-inspect"
+ condition: "CloudContentService exposes async-only disposal. Host shutdown must await it without requiring a synchronous disposeAll method; released leases return 404 and the host-owned cache root is removed."
+ - from: "capability.marketing-touch"
+ to: "capability.cloud-content-dialog"
+ type: "renders-through"
+ rank: "must-inspect"
+ - from: capability.marketing-touch
+ to: service.cloud-content-assets
+ type: resolves-resources-through
rank: must-inspect
- - from: service.conversation-topic-publisher
- to: state.conversation-projection
- type: delivers-snapshot-and-deltas-to
+ condition: Host owns validated ZIP leases; loopback URLs must not be reused by Web. Marketing Web bundle uses the declared fallback.
+ - from: "surface.start-plan-manual-claim"
+ to: "shared.cloud-dialog-hero"
+ type: "renders-result-hero-through"
+ rank: "must-inspect"
+ condition: "Server VisualBody is prepared before presentation; claim/report remain controller-owned, provider refresh uses ProviderSettingsService, and navigation never selects a model."
+ - from: "shared.cloud-dialog-hero"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-create-recovery-state-in"
+ rank: "invariant-only"
+
+ - from: "capability.app-usage"
+ to: "persistence.usage-observability-db"
+ type: "reads-authoritative-facts-from"
+ rank: "must-inspect"
+ condition: "App Usage reads model_usage, turn_usage, and tool_usage; it must not fall back to Coding Plan monitor data or session JSON estimates."
+ - from: "persistence.usage-observability-db"
+ to: "service.usage-stats-bridge"
+ type: "queries-through-protocol"
+ rank: "must-inspect"
+ - from: "service.usage-stats-bridge"
+ to: "surface.app-usage-dashboard"
+ type: "projects-snapshot-to"
+ rank: "must-inspect"
+ - from: "capability.app-usage"
+ to: "capability.plan-entitlements"
+ type: "must-remain-separate-from"
+ rank: "invariant-only"
+
+ - from: "capability.resource-manager"
+ to: "surface.resource-manager-window"
+ type: "projects-snapshot-to"
+ rank: "must-inspect"
+ condition: "The renderer polls window.resourceManager.getSnapshot() every 1s and keeps only group expansion state; all metrics come from the main-built snapshot."
+ - from: "capability.resource-manager"
+ to: "capability.data-observability"
+ type: "must-remain-separate-from"
+ rank: "invariant-only"
+ condition: "Shares the main-process Host/Agent pid registry and CLI MCP tracker, but never alters telemetry sampling cadence or ARMS payloads."
+ - from: "capability.resource-manager"
+ to: "capability.computer-use"
+ type: "conditional-attribution-of"
+ rank: "conditional"
+ condition: "Official computer-use MCP subtree is attributed to built-in plugins; the macOS CUA Helper launched via LaunchServices is not a Host descendant and is not attributed yet."
+
+ - from: "capability.data-observability"
+ to: "service.telemetry-core"
+ type: "reports-product-events-through"
+ rank: "must-inspect"
+ condition: "Product /report, ARMS/RUM, conversation telemetry, logging, and local App Usage remain distinct channels."
+ - from: "capability.data-observability"
+ to: "service.cli-agent-telemetry"
+ type: "reports-agent-runtime-traces-through"
+ rank: "must-inspect"
+ condition: "CLI agent OTLP traces remain distinct from product /report, ARMS/RUM, local logs, and local App Usage."
+ - from: "capability.renderer-user-action-trace"
+ to: "capability.data-observability"
+ type: "specializes-renderer-observability-within"
+ rank: "must-inspect"
+ condition: "Renderer ui_action uses a compiled catalog and bounded Desktop-only export; it does not reuse or mutate /event/report."
+ - from: "capability.data-observability"
+ to: "capability.ccbuddy-data-size-telemetry"
+ type: "contains"
+ rank: "must-inspect"
+ condition: "Desktop main schedules and reports one bounded local data-size snapshot while a Worker owns directory traversal."
+ - from: "capability.ccbuddy-data-size-telemetry"
+ to: "capability.development-storage-isolation"
+ type: "resolves-local-root-through"
+ rank: "must-inspect"
+ condition: "Default and custom data roots use getCCbuddyDataRootDir after dataBaseDir bootstrap."
+ - from: "capability.ccbuddy-data-size-telemetry"
+ to: "boundary.desktop-continuous"
+ type: "reports-outside-conversation-delivery"
+ rank: "invariant-only"
+ - from: "capability.ccbuddy-data-size-telemetry"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-report-through"
+ rank: "invariant-only"
+ - from: "capability.data-observability"
+ to: "service.mcp-session-runtime"
+ type: "observes-process-lifecycle-and-resource-of"
+ rank: "must-inspect"
+ condition: "Only successful stdio runtime instances, unexpected exits, first session startup counts and five-minute memory samples are reported; raw MCP names, commands, paths, credentials, stderr and PIDs remain local. A tracked process is only suspected orphaned after it has no pool owner for strictly more than 60 seconds and is still observed by the OS sampler."
+ - from: "service.mcp-session-runtime"
+ to: "boundary.desktop-continuous"
+ type: "reports-outside-conversation-delivery"
+ rank: "invariant-only"
+ condition: "MCP telemetry uses a strict process notification and trusted Host relay; it does not enter conversation frames, snapshots, queues or replay state."
+ - from: "capability.workspace-identity"
+ to: "service.telemetry-core"
+ type: "reports-remote-workspace-lifecycle-through"
+ rank: "must-inspect"
+ condition: "Only terminal remote connect outcomes and low-cardinality remote_kind/connect_trigger/error_category are reported; paths, identities, session ids, and remote target details must never enter telemetry."
+
+ - from: "capability.engineering-infrastructure"
+ to: "capability.build-release-engineering"
+ type: "contains"
+ rank: "must-inspect"
+ - from: "capability.model-io-full-retention"
+ to: "surface.model-io-full-retention-setting"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.model-io-full-retention-setting"
+ to: "persistence.app-settings"
+ type: "persists-to"
+ rank: "must-inspect"
+ - from: "persistence.app-settings"
+ to: "service.model-io-runtime-preference-sync"
+ type: "projects-to"
+ rank: "must-inspect"
+ - from: "service.model-io-runtime-preference-sync"
+ to: "persistence.model-io-jsonl"
+ type: "controls-retention-of"
+ rank: "must-inspect"
+ - from: "capability.model-io-full-retention"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.model-io-full-retention"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery"
+ rank: "invariant-only"
+ - from: "capability.model-io-full-retention"
+ to: "evidence.model-io-full-retention-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.development-storage-isolation"
+ to: "surface.data-directory-setting"
+ type: "renders-effective-root-in"
+ rank: "should-inspect"
+ - from: "surface.data-directory-setting"
+ to: "service.data-directory-setting"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "service.data-directory-setting"
+ to: "persistence.desktop-user-data-root"
+ type: "copies-and-points-to"
+ rank: "must-inspect"
+ - from: "capability.development-storage-isolation"
+ to: "persistence.desktop-user-data-root"
+ type: "selects-root-for"
+ rank: "must-inspect"
+ - from: "capability.development-storage-isolation"
+ to: "boundary.local-development-runtime"
+ type: "uses-dev-root-in"
+ rank: "must-inspect"
+ condition: "Only app.isPackaged=false uses the dev profile and .ccbuddy-dev root."
+ - from: "capability.development-storage-isolation"
+ to: "boundary.packaged-desktop-runtime"
+ type: "uses-stable-root-in"
+ rank: "must-inspect"
+ condition: "Packaged Stable and packaged Preview both use the stable profile and .ccbuddy root; CCBUDDY_ENV does not select storage."
+ - from: "persistence.desktop-user-data-root"
+ to: "boundary.workspace-key"
+ type: "must-not-rewrite-workspace-project-config"
+ rank: "invariant-only"
+ condition: "Workspace .ccbuddy files remain project content and are not renamed to .ccbuddy-dev."
+ - from: "capability.development-storage-isolation"
+ to: "evidence.development-storage-isolation"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.build-release-engineering"
+ to: "capability.embedded-native-search"
+ type: "produces-runtime-assets-for"
+ rank: "must-inspect"
+ - from: "capability.embedded-native-search"
+ to: "service.native-search-release-plan"
+ type: "versioned-and-validated-by"
+ rank: "must-inspect"
+ - from: "service.native-search-release-plan"
+ to: "persistence.native-search-shared-deps"
+ type: "publishes-immutable-archives-to"
+ rank: "must-inspect"
+ - from: "capability.embedded-native-search"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "capability.embedded-native-search"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "capability.engineering-infrastructure"
+ to: "capability.test-automation-platform"
+ type: "contains"
+ rank: "must-inspect"
+ - from: "capability.engineering-infrastructure"
+ to: "capability.quality-intelligence"
+ type: "contains"
+ rank: "must-inspect"
+ - from: "capability.test-automation-platform"
+ to: "capability.quality-intelligence"
+ type: "publishes-test-results-to"
+ rank: "must-inspect"
+ - from: "capability.build-release-engineering"
+ to: "capability.quality-intelligence"
+ type: "is-gated-by"
+ rank: "should-inspect"
+ - from: "capability.build-release-engineering"
+ to: "capability.desktop-app-shutdown"
+ type: "produces-update-artifacts-consumed-by"
+ rank: "must-inspect"
+ condition: "CI owns build, signing, notarization, artifact upload, and release promotion; the Host runtime owns update checks, download, and install handoff."
+ - from: "capability.quality-intelligence"
+ to: "capability.data-observability"
+ type: "must-remain-separate-from"
+ rank: "invariant-only"
+ condition: "Test results, coverage, failure artifacts, and engineering trends must not be reported as product telemetry, ARMS/RUM, Conversation telemetry, or App Usage."
+
+ - from: "capability.office-computer-use-incubation"
+ to: "capability.office-suite"
+ type: "contains"
+ rank: "must-inspect"
+ - from: "capability.office-computer-use-incubation"
+ to: "capability.computer-use"
+ type: "contains"
+ rank: "must-inspect"
+ - from: "capability.office-suite"
+ to: "surface.office-document-workbench"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "PDF, DOCX, XLSX, and PPTX preview exist today; Preview Pane PPTX remains read-only and native Office editing is still bounded separately."
+ - from: "surface.office-document-workbench"
+ to: "service.office-document-platform"
+ type: "commits-to"
+ rank: "conditional"
+ condition: "Planned native Office editing must use a workspace-scoped Host service rather than direct Renderer Node or LibreOffice IO."
+ - from: "service.document-skills"
+ to: "capability.office-suite"
+ type: "produces-workspace-artifacts-for"
+ rank: "must-inspect"
+ condition: "The official plugin provides DOCX/PDF production and a narrow PPTX reference resolver; it is independent from a future native editor draft."
+ - from: "service.document-skills"
+ to: "surface.office-document-workbench"
+ type: "produces-previewable-artifacts-for"
+ rank: "should-inspect"
+ - from: "capability.pptx-element-reference"
+ to: "surface.pptx-element-selection"
+ type: "selects-in"
+ rank: "must-inspect"
+ - from: "surface.pptx-element-selection"
+ to: "state.pptx-preview-element-selection"
+ type: "owns-local-draft"
+ rank: "must-inspect"
+ condition: "Selection is generation-scoped renderer state; source/page changes clear local selection without deleting Composer references."
+ - from: "surface.pptx-element-selection"
+ to: "state.pptx-preview-ai-edit-draft"
+ type: "owns-local-draft"
+ rank: "must-inspect"
+ condition: "AI Edit captures optional selectedText and opens a local comment draft aligned with the existing code-comment interaction; Composer remains unchanged until explicit confirmation."
+ - from: "state.pptx-preview-ai-edit-draft"
+ to: "surface.composer-presentation-reference"
+ type: "commits-on-confirmation-to"
+ rank: "must-inspect"
+ condition: "Cancel discards only the local draft; Add to conversation trims optional comment, dispatches one reference, and keeps the selected element."
+ - from: "state.pptx-preview-ai-edit-draft"
+ to: "boundary.web-remote-replayable"
+ type: "must-remain-isolated-from"
+ rank: "invariant-only"
+ condition: "The unsent instruction draft is renderer-local and never enters task snapshots, replay, relay, main, Host, or Agent runtime state."
+ - from: "surface.pptx-element-selection"
+ to: "surface.composer-presentation-reference"
+ type: "adds-structured-reference-to"
+ rank: "must-inspect"
+ condition: "Only the focused primary Composer in the same workspace and remote-session scope accepts the event."
+ - from: "surface.composer-presentation-reference"
+ to: "state.composer-presentation-reference-draft"
+ type: "owns-local-draft"
+ rank: "must-inspect"
+ - from: "surface.composer-presentation-reference"
+ to: "state.pptx-reference-preview-navigation"
+ type: "opens-referenced-page-through"
+ rank: "must-inspect"
+ condition: "A concrete draft or historical reference item exits viewer selection, opens or reuses the scoped PPTX preview, and best-effort navigates by a repeatable request id."
+ - from: "state.pptx-reference-preview-navigation"
+ to: "surface.office-document-workbench"
+ type: "projects-to"
+ rank: "must-inspect"
+ condition: "Missing file and missing page use separate toasts; a readable file remains open when only the referenced page cannot be located."
+ - from: "state.pptx-reference-preview-navigation"
+ to: "boundary.web-remote-replayable"
+ type: "must-remain-isolated-from"
+ rank: "invariant-only"
+ condition: "Navigation is renderer-local UI intent and does not enter task snapshot, replay, relay, main, or Agent runtime state."
+ - from: "state.composer-presentation-reference-draft"
+ to: "boundary.workspace-key"
+ type: "isolated-by"
+ rank: "must-inspect"
+ condition: "workspaceIdentity?.trim() || workspacePath, remoteSessionId, and draft/session scope remain distinct coordinates."
+ - from: "state.composer-presentation-reference-draft"
+ to: "boundary.web-remote-replayable"
+ type: "must-remain-isolated-from"
+ rank: "invariant-only"
+ condition: "Unsent references remain renderer-local; sent prompt text follows existing replayable delivery without new relay or main state."
+ - from: "capability.pptx-element-reference"
+ to: "service.pptx-reference-resolver"
+ type: "mutates-through"
+ rank: "must-inspect"
+ condition: "textFingerprint validates the full shape or table-cell text; optional selectedText and comment are model context only and never replace the resolver conflict object. New prompt tails name presentation element comments and explicitly require per-reference comment handling without depending on skill loading."
+ - from: "service.pptx-reference-resolver"
+ to: "boundary.workspace-key"
+ type: "preserves-execution-scope"
+ rank: "must-inspect"
+ condition: "The Agent executes against workspacePath after exact source fingerprint and OOXML locator validation; renderer and relay do not own file IO."
+ - from: "capability.computer-use"
+ to: "surface.computer-use-task-experience"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.computer-use-task-experience"
+ to: "state.windows-cua-operation-indicator"
+ type: "projects-tool-triggered-state-through"
+ rank: "must-inspect"
+ condition: "Desktop-local Windows only; each official CUA tool-started refreshes a 10-second safety deadline, while terminal/runtime-unavailable events clear the projection."
+ - from: "state.windows-cua-operation-indicator"
+ to: "boundary.cua-desktop-local"
+ type: "must-remain-local-to"
+ rank: "invariant-only"
+ condition: "The indicator is a Main-owned ephemeral BrowserWindow and never enters web-remote replayable state, snapshots, relay, or a second runtime."
+ - from: "state.windows-cua-operation-indicator"
+ to: "capability.desktop-startup-critical-path"
+ type: "must-not-enter-business-window-routing"
+ rank: "invariant-only"
+ condition: "CUA indicator windows are excluded from Explorer/deep-link target selection and primary application window resolution."
+ - from: "capability.computer-use"
+ to: "service.cua-platform"
+ type: "implemented-by"
+ rank: "must-inspect"
+ - from: "capability.computer-use"
+ to: "service.cua-helper-build-release"
+ type: "packaged-by"
+ rank: "must-inspect"
+ condition: "Stable builds assemble CCbuddy Computer Use.app; local development builds assemble CCbuddy Computer Use Dev.app. Both use the CCbuddy Computer Use executable and producer-owned identity constants."
+ - from: "capability.build-release-engineering"
+ to: "service.cua-helper-build-release"
+ type: "produces-signed-runtime-through"
+ rank: "must-inspect"
+ - from: "service.cua-helper-build-release"
+ to: "service.cua-platform"
+ type: "supplies-canonical-helper-to"
+ rank: "must-inspect"
+ condition: "Build, package, CI staging, signing, notarization, installation, and diagnostics must resolve the same producer-owned app, display, executable, bundle, and team identities."
+ - from: "service.cua-helper-build-release"
+ to: "boundary.packaged-desktop-runtime"
+ type: "installs-into"
+ rank: "must-inspect"
+ - from: "capability.computer-use"
+ to: "state.cua-frame-pixel-authority"
+ type: "issues-and-consumes-immutable-frames-through"
+ rank: "must-inspect"
+ condition: "Screenshot, get_app_state window rasters and Zoom child rasters all project model pixels through one producer frame registry and Helper native gate."
+ - from: "state.cua-frame-pixel-authority"
+ to: "service.cua-platform"
+ type: "dispatches-windowserver-verified-pixels-through"
+ rank: "must-inspect"
+ condition: "Capture and dispatch use the same WindowServer owner policy; element/AX actions remain a separate semantic path."
+ - from: "state.cua-frame-pixel-authority"
+ to: "service.cua-tool-contract-projection"
+ type: "requires-byte-and-adjacency-preservation-from"
+ rank: "must-inspect"
+ - from: "state.cua-frame-pixel-authority"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-enter-replayable-state"
+ rank: "invariant-only"
+ condition: "Frame registry, WindowServer snapshots and native dispatch remain desktop-local ephemeral authority."
+ - from: "capability.computer-use"
+ to: "service.cua-plugin-dev-runtime-assets"
+ type: "requires-model-runtime-from"
+ rank: "must-inspect"
+ condition: "Computer Use Helper readiness is independent from the shared node_repl SDK/skill assets."
+ - from: "capability.computer-use"
+ to: "service.cua-node-repl-bridge"
+ type: "projects-through-shared-node-repl"
+ rank: "must-inspect"
+ condition: "The model invokes only mcp__node_repl__js; the bridge bootstraps agent.computerUse per Worker."
+ - from: "service.cua-node-repl-bridge"
+ to: "state.cua-shared-host-runtime"
+ type: "routes-requests-to"
+ rank: "must-inspect"
+ condition: "Authenticated workspace/session context selects one shared-host CUA runtime session."
+ - from: "state.cua-shared-host-runtime"
+ to: "boundary.cua-desktop-local"
+ type: "owns-ephemeral-authority-within"
+ rank: "invariant-only"
+ condition: "Native Helper/TCC and action safety remain desktop-local and fail closed."
+ - from: "capability.build-release-engineering"
+ to: "service.cua-plugin-dev-runtime-assets"
+ type: "produces-dev-runtime-assets-for"
+ rank: "must-inspect"
+ condition: "Ordinary desktop Dev builds must stage and validate the CUA SDK/docs/Skill before the Agent bundle is started; Browser owns the only node_repl MCP."
+ - from: "service.cua-plugin-dev-runtime-assets"
+ to: "service.cua-node-repl-bridge"
+ type: "supplies-sdk-assets-to"
+ rank: "must-inspect"
+ condition: "Filesystem seed must copy the CUA SDK/docs/Skill into the official plugin cache before shared node_repl bootstraps a Worker."
+ - from: "service.cua-plugin-dev-runtime-assets"
+ to: "boundary.cua-desktop-local"
+ type: "must-not-bypass"
+ rank: "invariant-only"
+ condition: "A valid MCP artifact never bypasses product enablement, broker socket/token, Helper trust, or macOS TCC authorization."
+ - from: "capability.computer-use"
+ to: "service.cua-node-repl-bridge"
+ type: "executes-sdk-through"
+ rank: "must-inspect"
+ condition: "Current model calls enter only through node_repl js and the agent.computerUse SDK."
+ - from: "service.cua-tool-contract-projection"
+ to: "surface.computer-use-task-experience"
+ type: "historical-transcript-only"
+ rank: "evidence-only"
+ - from: "service.cua-tool-contract-projection"
+ to: "service.cua-platform"
+ type: "removed-execution-path"
+ rank: "evidence-only"
+ condition: "Legacy CUA title/alias projection is retained only for read-only transcript parsing; new execution never dispatches these entries."
+ - from: "service.cua-platform"
+ to: "state.cua-action-state-authority"
+ type: "owns-mutation-epoch-and-post-state"
+ rank: "must-inspect"
+ condition: "ccbuddy-cua invalidates pre-action state ids and only publishes synced after a full authoritative observation."
+ - from: "service.cua-tool-contract-projection"
+ to: "state.cua-action-state-authority"
+ type: "historical-transcript-only"
+ rank: "evidence-only"
+ condition: "Historical transcripts remain readable; current action-state returns through the generic node_repl result."
+ - from: "state.cua-action-state-authority"
+ to: "state.conversation-projection"
+ type: "must-not-change-session-semantics"
+ rank: "invariant-only"
+ condition: "The bounded tool result follows existing lifecycle/history; no new conversation state, queue, owner, snapshot, or replay semantics are introduced."
+ - from: "service.cua-platform"
+ to: "boundary.cua-desktop-local"
+ type: "authorized-by"
+ rank: "must-inspect"
+ condition: "Current product CUA is enabled by default but remains desktop-local: macOS uses the signed Helper/TCC boundary, while Windows uses the packaged Host-managed Helper runtime; explicit 0/false/off disables it and both platforms fail closed."
+ - from: "service.cua-platform"
+ to: "capability.agent-core-runtime"
+ type: "must-remain-isolated-from"
+ rank: "must-inspect"
+ condition: "Helper health is checked only at explicit CUA demand boundaries; periodic probes and timer-triggered recovery are forbidden, and every Helper lifecycle result leaves Agent process/session ownership unchanged."
+ - from: "surface.computer-use-task-experience"
+ to: "state.conversation-projection"
+ type: "projects-from"
+ rank: "should-inspect"
+ condition: "Current UI reuses generic tool/interaction projection; a dedicated live control and takeover protocol is still planned."
+
+ - from: "capability.desktop-app-shutdown"
+ to: "service.desktop-app-shutdown-policy"
+ type: "selects-budget-through"
+ rank: "must-inspect"
+ condition: "Windows normal quit uses the short frequent-operation budget; update-install keeps the strict resource-release barrier."
+ - from: "service.desktop-app-shutdown-policy"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "service.desktop-app-shutdown-policy"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+
+ - from: "capability.desktop-startup-critical-path"
+ to: "service.desktop-local-host-startup"
+ type: "starts-local-host-through"
+ rank: "must-inspect"
+ - from: "service.desktop-local-host-startup"
+ to: "state.desktop-runtime-process-env"
+ type: "consumes-bounded-patch-from"
+ rank: "must-inspect"
+ condition: "Environment prewarm is advisory and bounded; failure or timeout passes a complete shell-free fallback patch and never re-enters synchronous login-shell capture in Local Host."
+ - from: "capability.desktop-startup-critical-path"
+ to: "state.desktop-tab-restore-projection"
+ type: "restores-active-first-through"
+ rank: "must-inspect"
+ - from: "state.desktop-tab-restore-projection"
+ to: "persistence.app-settings"
+ type: "restores-from-and-persists-complete-state-to"
+ rank: "must-inspect"
+ condition: "The active-ready projection is transient and cannot overwrite lastWorkspaceSession; a user-closed active tab cannot be resurrected by deferred completion."
+ - from: "service.desktop-local-host-startup"
+ to: "boundary.desktop-continuous"
+ type: "owned-locally-by"
+ rank: "must-inspect"
+ - from: "service.desktop-local-host-startup"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-create-independent-host-for"
+ rank: "invariant-only"
+ - from: "state.desktop-tab-restore-projection"
+ to: "boundary.workspace-key"
+ type: "preserves-isolation"
+ rank: "must-inspect"
+ - from: "capability.desktop-startup-critical-path"
+ to: "evidence.desktop-startup-critical-path-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.project-memory"
+ to: "service.project-memory-extraction-lifecycle"
+ type: "runs-background-through"
+ rank: "must-inspect"
+ - from: "capability.project-memory"
+ to: "service.headless-cli-prompt-runtime"
+ type: "preserves-main-memory-in"
+ rank: "should-inspect"
+ condition: "`-p/--prompt` and `--target` retain the active Main Memory prompt/index or Recall branch, custom-agent Memory and session persistence."
+ - from: "service.headless-cli-prompt-runtime"
+ to: "service.project-memory-extraction-lifecycle"
+ type: "suppresses-automatic-scheduling-in"
+ rank: "must-inspect"
+ condition: "By default both non-interactive entry points materialize `memory.extractionEnabled=false`; the scheduler returns before enabled-root resolution, snapshot acquisition, provider work, cursor updates or file mutation. TUI and app-server keep the default enabled lifecycle."
+ - from: "service.headless-cli-prompt-runtime"
+ to: "service.project-memory-extraction-lifecycle"
+ type: "waits-before-close-for"
+ rank: "must-inspect"
+ condition: "Explicit --memory-bench with -p/--prompt enables Extraction for this invocation and waits without a drain deadline before normal close. Effective Project Memory must already be enabled; signals retain cancellation, and other entry points keep their existing close policy."
+ - from: "capability.project-memory"
+ to: "surface.project-memory-settings"
+ type: "renders-read-only-local-catalog-in"
+ rank: "must-inspect"
+ - from: "surface.project-memory-settings"
+ to: "persistence.app-settings"
+ type: "commits-enable-toggle-to"
+ rank: "must-inspect"
+ condition: "The toggle remains session-scoped; existing runtimes keep their materialized value."
+ - from: surface.project-memory-settings
+ to: service.project-memory-catalog
+ type: reads-local-snapshots-through
rank: must-inspect
+ condition: Only the Desktop local-Host viewer reads the catalog, and it performs no catalog read while Memory is disabled. The UI navigates project list -> project memories -> one document and reads document content only after explicit file selection. Web render a local-desktop notice instead.
+ - from: "service.project-memory-catalog"
+ to: "persistence.project-memory-files"
+ type: "reads-without-mutating"
+ rank: "must-inspect"
+ - from: surface.project-memory-settings
+ to: boundary.web-remote-replayable
+ type: must-not-add-remote-aggregation-to
+ rank: invariant-only
+ condition: Web keep the Memory toggle but do not render or query the Project Memory viewer.
+ - from: "surface.project-memory-settings"
+ to: "boundary.desktop-continuous"
+ type: "must-not-trigger-provider-or-runtime"
+ rank: "invariant-only"
+ - from: "service.project-memory-extraction-lifecycle"
+ to: "capability.conversation-runtime"
+ type: "cancels-on-session-close-with"
+ rank: "must-inspect"
+ condition: "Session close rejects new Extraction scheduling, drops latest pending, aborts running work, then performs bounded drain before owned resources close."
+ - from: "service.project-memory-extraction-lifecycle"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "service.project-memory-extraction-lifecycle"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+
+ - from: "capability.provider-templates"
+ to: "capability.provider-registry"
+ type: "supplies-inheritance-baseline-to"
+ rank: "must-inspect"
+ condition: "Resolver applies at most one Template baseline before Concrete Provider layers; Registry receives only the completed Effective Provider with its real providerId."
+ - from: "capability.provider-templates"
+ to: "surface.provider-settings"
+ type: "creates-personal-provider-through"
+ rank: "must-inspect"
+ condition: "Template selection creates a new Personal Provider identity; editing or deleting that instance never mutates the Built-in Template."
+ - from: "capability.remote-provider-provisioning"
+ to: "capability.provider-registry"
+ type: "projects-local-config-into-remote-environment-for"
+ rank: "must-inspect"
+ condition: "A replace-style envelope updates the Remote Environment stores and refreshes its Registry; it does not transfer a Registry Snapshot or mutate existing Active Models."
+ - from: "capability.remote-provider-provisioning"
+ to: "capability.workspace-identity"
+ type: "deduplicates-by-remote-environment-beyond"
+ rank: "must-inspect"
+ condition: "Environment identity owns synchronization lanes; workspaceIdentity, workspacePath and remoteSessionId remain workspace/session identities and are not Provisioning deduplication keys."
+ - from: "capability.remote-provider-provisioning"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "capability.remote-provider-provisioning"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+
+ - from: "capability.model-capabilities"
+ to: "capability.model-selection"
+ type: "owns-capability-boundary-for"
+ rank: "must-inspect"
+ - from: "capability.model-capabilities"
+ to: "capability.provider-registry"
+ type: "owns-capability-boundary-for"
+ rank: "must-inspect"
+ - from: "capability.model-capabilities"
+ to: "service.active-session-model-limits"
+ type: "projects-selected-model-limits-through"
+ rank: "must-inspect"
+ condition: "Context window and max output tokens come from the Model selected for the execution; input/output formats stay on Model.properties and are not copied into a runtime capability map."
+ - from: "capability.model-capabilities"
+ to: "capability.conversation-runtime"
+ type: "configures-runtime-for"
+ rank: "must-inspect"
+ - from: "capability.model-capabilities"
+ to: "boundary.ccbuddy-cli-provider-transport"
+ type: "maps-effective-options-into-final-request-body-for"
+ rank: "must-inspect"
+ condition: "Active Model option maps apply ordered RFC 7396 patches to the final serialized JSON body before request signing; debug and Model IO observe the patched body, and no parallel reasoning/max-output mapping remains."
+ - from: "capability.model-capabilities"
+ to: "surface.user-subagent-form"
+ type: "options-from"
+ rank: "must-inspect"
+ condition: "A matching workspace-config modelThoughtLevels declaration wins; a missing target model or capability field falls back to App provider metadata without waiting for configOptionsStatus, while catalog reconciliation continues independently in the background."
+ - from: "capability.model-capabilities"
+ to: "surface.builtin-subagent-model"
+ type: "options-from"
+ rank: "must-inspect"
+ condition: "A matching workspace-config modelThoughtLevels declaration wins; a missing target model or capability field falls back to App provider metadata without waiting for configOptionsStatus, while catalog reconciliation continues independently in the background."
+
+ - from: "capability.model-selection"
+ to: "surface.conversation-model-control"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.model-selection"
+ to: "surface.automation-edit"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.model-selection"
+ to: "surface.user-subagent-form"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.model-selection"
+ to: "surface.builtin-subagent-model"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.provider-registry"
+ to: "surface.provider-settings"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.model-selection"
+ to: "capability.provider-registry"
+ type: "options-from"
+ rank: "should-inspect"
+ - from: "capability.model-selection"
+ to: "service.host-model-selection"
+ type: "implemented-by"
+ rank: "must-inspect"
+ condition: "The target Workspace Host atomically supplies visible candidates, option specs, and preferredSelection; App Recent stays Renderer-owned and workspaceKey-scoped, while the target Worker Registry and ModelFactory retain final execution validation."
+ - from: "service.host-model-selection"
+ to: "state.renderer-model-selection-read"
+ type: "is-read-through"
+ rank: "must-inspect"
+ condition: "Renderer distinguishes loading, target unavailable, first-read error, and ready; owner changes discard stale results, revisions are monotonic, reload is explicit, and a Ready last-known-good survives background refresh failure."
+ - from: "state.renderer-model-selection-read"
+ to: "shared-ui.model-select-groups"
+ type: "permits-projection-only-when-ready"
+ rank: "must-inspect"
+ - from: "state.renderer-model-selection-read"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-fallback-to-local-host-in"
+ rank: "must-inspect"
+ condition: "Remote waiting or disconnection is unavailable; it never reads Base/Local Host candidates and does not change task replay semantics."
+ - from: "service.host-model-selection"
+ to: "surface.conversation-model-control"
+ type: "supplies-target-environment-options-to"
+ rank: "must-inspect"
+ condition: "Local panes read Local Host; remote panes read Remote Host; remote waiting or disconnection never falls back to Base/Local Host."
+ - from: "service.host-model-selection"
+ to: "surface.automation-edit"
+ type: "supplies-selected-workspace-options-to"
+ rank: "must-inspect"
+ - from: "service.provider-settings"
+ to: "surface.provider-settings"
+ type: "supplies-lifecycle-view-to"
+ rank: "must-inspect"
+ condition: "Initial loading, first-read error with reload, and Ready ProviderSettingsView remain distinct; a Ready last-known-good survives background refresh failure."
+ - from: "service.host-model-selection"
+ to: "surface.user-subagent-form"
+ type: "supplies-local-environment-options-to"
+ rank: "must-inspect"
+ condition: "Subagent Settings manages Local Environment config only; remote Environment Subagent config remains independently persisted and is not editable by this UI phase."
+ - from: "service.host-model-selection"
+ to: "surface.builtin-subagent-model"
+ type: "supplies-local-environment-options-to"
+ rank: "must-inspect"
+ condition: "Built-in Subagent overrides edited in Settings belong to Local Environment only; remote overrides remain owned by the Remote Environment."
+
+ - from: "capability.anthropic-request-metadata"
+ to: "service.anthropic-request-options"
+ type: "implemented-by"
+ rank: "must-inspect"
+ - from: "service.anthropic-request-options"
+ to: "capability.provider-registry"
+ type: "applies-to-provider-kind"
+ rank: "must-inspect"
+ condition: "Only providerKind=anthropic receives metadata.user_id."
+ - from: "service.anthropic-request-options"
+ to: "service.cli-device-mid"
+ type: "reads-device-identity-through"
+ rank: "must-inspect"
+ - from: "service.cli-device-mid"
+ to: "persistence.telemetry-device-mid"
+ type: "reads-creates"
+ rank: "must-inspect"
+ condition: "The actual local or remote Agent runtime uses its own CCBUDDY_DATA_BASE_DIR and shares the deviceMid field with Desktop/telemetry when colocated."
+ - from: "service.telemetry-core"
+ to: "persistence.telemetry-device-mid"
+ type: "shares-device-identity-with"
+ rank: "must-inspect"
+ - from: "service.anthropic-request-options"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "service.anthropic-request-options"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+
+ - from: "capability.model-request-session-type-attribution"
+ to: "service.model-request-session-type-attribution"
+ type: "implemented-by"
+ rank: "must-inspect"
+ - from: "service.model-request-session-type-attribution"
+ to: "boundary.ccbuddy-cli-provider-transport"
+ type: "injects-managed-request-header-at"
+ rank: "must-inspect"
+ condition: "Every generateText, streamText, retry, and recovery request sends exactly one x-ccbuddy-session-type value; managed attribution overrides a conflicting provider static header."
+ - from: "service.model-request-session-type-attribution"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "service.model-request-session-type-attribution"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "capability.model-request-session-type-attribution"
+ to: "evidence.model-request-session-type-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.anthropic-reasoning-history-replay"
+ to: "service.anthropic-reasoning-history-projection"
+ type: "implemented-by"
+ rank: "must-inspect"
+ - from: "capability.model-reasoning-request-projection"
+ to: "service.model-reasoning-request-projection"
+ type: "implemented-by"
+ rank: "must-inspect"
+ - from: "service.model-reasoning-request-projection"
+ to: "boundary.ccbuddy-cli-provider-transport"
+ type: "projects-request-local-history-to"
+ rank: "must-inspect"
+ condition: "Exact-empty reasoning blocks without provider options are omitted only from serializer input; opaque provider metadata, whitespace-only reasoning, OpenAI Responses references, and provider-required tool-call placeholders remain unchanged, while canonical and persisted history are not mutated."
+ - from: "service.anthropic-reasoning-history-projection"
+ to: "boundary.ccbuddy-cli-provider-transport"
+ type: "projects-request-local-history-to"
+ rank: "must-inspect"
+ condition: "Only Anthropic requests normalize reasoning history, exactly once per logical request before its physical attempts; ordinary and signature-repair retries reuse or derive from that request-local projection, while canonical MessageHistory and persisted messages remain unchanged."
+ - from: "persistence.assistant-message-model-provenance"
+ to: "state.message-history-model-provenance"
+ type: "hydrates-model-provenance-into"
+ rank: "must-inspect"
+ condition: "Persisted assistant providerID, modelID, and variant restore ModelInputMessage.modelRef without adding a storage migration."
+ - from: "state.message-history-model-provenance"
+ to: "service.anthropic-reasoning-history-projection"
+ type: "supplies-source-model-identity-to"
+ rank: "must-inspect"
+ condition: "Live result.model and resumed assistant metadata supply the same providerId + modelId identity; missing provenance remains unknown rather than being inferred as cross-model."
+ - from: "service.anthropic-reasoning-history-projection"
+ to: "state.conversation-projection"
+ type: "reports-signature-repair-through"
+ rank: "should-inspect"
+ condition: "A confirmed signature rejection reuses model_retry_scheduled and the existing control.apiRetry live-tail state."
+ - from: "service.anthropic-reasoning-history-projection"
+ to: "persistence.usage-observability-db"
+ type: "counts-physical-retry-through"
+ rank: "should-inspect"
+ condition: "Usage counts the existing model_retry_scheduled event; no separate repair counter or persistence path is introduced."
+
+ - from: "surface.conversation-model-control"
+ to: "shared-ui.model-config-select"
+ type: "shares-component"
+ rank: "must-inspect"
+ - from: "surface.automation-edit"
+ to: "shared-ui.model-config-select"
+ type: "shares-component"
+ rank: "must-inspect"
+ - from: "surface.user-subagent-form"
+ to: "shared-ui.model-config-select"
+ type: "shares-component"
+ rank: "must-inspect"
+ - from: "surface.builtin-subagent-model"
+ to: "shared-ui.model-config-select"
+ type: "shares-component"
+ rank: "must-inspect"
+
+ - from: "surface.conversation-model-control"
+ to: "shared-ui.model-select-groups"
+ type: "options-from"
+ rank: "should-inspect"
+ - from: "surface.automation-edit"
+ to: "shared-ui.model-select-groups"
+ type: "options-from"
+ rank: "should-inspect"
+ - from: "surface.user-subagent-form"
+ to: "shared-ui.model-select-groups"
+ type: "options-from"
+ rank: "should-inspect"
+ - from: "surface.builtin-subagent-model"
+ to: "shared-ui.model-select-groups"
+ type: "options-from"
+ rank: "should-inspect"
+
+ - from: "shared-ui.model-select-groups"
+ to: "service.host-model-selection"
+ type: "projects-ready-view-from"
+ rank: "must-inspect"
+ condition: "The builder only accepts a Ready target-host ModelSelectionView; loading, unavailable, and error remain caller-owned lifecycle states."
+ - from: "capability.model-capabilities"
+ to: "shared-ui.model-select-groups"
+ type: "projects-vision-badge-from-input-format"
+ rank: "must-inspect"
+ condition: "Any enabled non-text properties.input_format leaf marks the model item as Vision; model ids remain opaque strings, including nested slashes."
+ - from: "capability.model-capabilities"
+ to: "surface.provider-settings"
+ type: "renders-vision-badge-in"
+ rank: "must-inspect"
+ condition: "The compact model row renders Vision beside context capacity without changing model metadata or persistence."
+ - from: "surface.provider-settings"
+ to: "service.provider-settings"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "surface.provider-settings"
+ to: "capability.provider-registry"
+ type: "configures-explicit-model-input-format-for"
+ rank: "must-inspect"
+ condition: "Text is locked for input, output is text-only, Image/Video are editable, hidden Audio/PDF leaves are preserved, and changes affect Models created after the Registry refresh."
+ - from: "surface.provider-settings"
+ to: "capability.provider-registry"
+ type: "configures-provider-owned-model-membership-activation-and-order-for"
+ rank: "must-inspect"
+ condition: "Built-in ids are read-only, Personal ids are editable, Effective Model Config enabled controls activation, and providerOrder/modelOrder control sorting. Disabled members remain editable but cannot be tested or created; Built-in wins duplicate ids and later duplicates are ignored then normalized on save."
+ - from: "surface.provider-settings"
+ to: "capability.provider-registry"
+ type: "resolves-model-config-rules-for-draft-display"
+ rank: "must-inspect"
+ condition: "Add/edit Model dialogs share resolveModelConfig and a 1200ms idle/blur flush trigger; persisted Personal Config remains sparse, Renderer discards stale resolutions, and only reports loaded defaults when inheritedConfig actually changes."
+ - from: "capability.provider-registry"
+ to: "surface.provider-settings"
+ type: "renders-builtin-provider-connection-facts-read-only-in"
+ rank: "must-inspect"
+ condition: "Standard Built-in Providers display the same Base URL and API Schema facts as Personal Providers, but freeze those Built-in-owned fields; API Key and Personal Model Config permissions remain independent."
+ - from: "capability.provider-registry"
+ to: "shared-ui.model-select-groups"
+ type: "projects-visible-enabled-candidates-to"
+ rank: "must-inspect"
+ condition: "Only executable members from visible Providers enter user candidates. Hidden Providers remain available to exact internal Registry creation; disabled, incomplete, or duplicate-later members do not enter Registry or fallback, and candidate order is preserved without UI re-sorting."
+ - from: "service.model-provider"
+ to: "persistence.model-provider-config"
+ type: "persists-through"
+ rank: "must-inspect"
+ - from: "persistence.model-provider-config"
+ to: "capability.provider-registry"
+ type: "projects-explicit-input-output-format-into"
+ rank: "must-inspect"
+ - from: "service.model-provider"
+ to: "capability.provider-registry"
+ type: "owns-registry-for"
+ rank: "must-inspect"
+ - from: "capability.provider-registry"
+ to: "service.active-session-model-limits"
+ type: "supplies-explicitly-selected-model-limits-to"
+ rank: "must-inspect"
+ condition: "Selection/admission supplies the chosen Model limits; a Provider Config refresh alone must not hot-mutate an already created Active Model."
+ - from: "service.active-session-model-limits"
+ to: "capability.conversation-runtime"
+ type: "updates-runtime-config-for"
+ rank: "must-inspect"
+ - from: "service.active-session-model-limits"
+ to: "state.conversation-projection"
+ type: "projects-capacity-to"
+ rank: "must-inspect"
+ condition: "A same-model ModelSelected replaces maxTokens, preserves usedTokens, and uses null for unknown capacity."
+
+ - from: "surface.conversation-model-control"
+ to: "state.conversation-projection"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "capability.conversation-runtime"
+ to: "surface.conversation-query-edit"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.provider-retry-live-tail"
+ to: "state.conversation-projection"
+ type: "projects-from"
+ rank: "must-inspect"
+ condition: "ProductProjection owns the authoritative control.apiRetry runtime state; UI does not infer retry from transport or elapsed time."
+ - from: "capability.provider-retry-live-tail"
+ to: "surface.subagent-session-detail"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "Main and read-only child details share ConversationTimeline; child display does not enable manual retry."
+ - from: "capability.provider-retry-live-tail"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ condition: "The <=2 presentation threshold must not alter desktop direct continuous delivery or runtime snapshot authority."
+ - from: capability.provider-retry-live-tail
+ to: boundary.web-remote-replayable
+ type: must-not-change-recovery-semantics
+ rank: invariant-only
+ condition: The <=2 presentation threshold must not alter Web/SSH replayable gap/snapshot recovery.
+ - from: "capability.provider-retry-live-tail"
+ to: "evidence.conversation-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+ condition: "SAT25, SAT25a and SAT25b define the threshold equivalence classes and their loading/retry assertions."
+ - from: "capability.model-stream-recovery"
+ to: "capability.provider-retry-live-tail"
+ type: "projects-recovery-status-through"
+ rank: "must-inspect"
+ condition: "Adapter pre-output retries and core safe-anchor recovery share control.apiRetry, but reasoning remains real-time and therefore recovers in core after crossing the adapter boundary."
+ - from: "capability.model-stream-recovery"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "capability.model-stream-recovery"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "capability.model-stream-recovery"
+ to: "evidence.conversation-fault-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+ condition: "Fault subcase S02-R1 defines reasoning-only retryable interruption recovery while preserving tool, cancellation, queue, and delivery boundaries."
+ - from: "capability.model-empty-completion-retry"
+ to: "capability.provider-retry-live-tail"
+ type: "projects-recovery-status-through"
+ rank: "must-inspect"
+ condition: "Generic zero-output completions publish the same model_request_failed/model_retry_scheduled status path while the adapter-specific empty-completion budget remains capped at one retry."
+ - from: "capability.ask-user-question-auto-resolution"
+ to: "surface.ask-user-question-auto-resolution-setting"
+ type: "configured-in"
+ rank: "must-inspect"
+ - from: "surface.ask-user-question-auto-resolution-setting"
+ to: "persistence.app-settings"
+ type: "persists-to"
+ rank: "must-inspect"
+ - from: "persistence.app-settings"
+ to: "service.interaction-auto-resolution-runtime"
+ type: "syncs-runtime-preference-to"
+ rank: "must-inspect"
+ condition: "Each renderer syncs the app-global preference to its connected local or remote host and cached remote Bot runtimes; inactive workspaces and uncached Bot targets do not spawn a Host or CLI only for preference delivery."
+ - from: "service.interaction-auto-resolution-runtime"
+ to: "surface.parent-task-blocking-interaction"
+ type: "projects-authoritative-state-to"
+ rank: "must-inspect"
+ - from: "service.interaction-auto-resolution-runtime"
+ to: "boundary.workspace-key"
+ type: "routes-by"
+ rank: "must-inspect"
+ - from: "service.interaction-auto-resolution-runtime"
+ to: "boundary.desktop-continuous"
+ type: "projects-through"
+ rank: "invariant-only"
+ - from: "service.interaction-auto-resolution-runtime"
+ to: "boundary.web-remote-replayable"
+ type: "recovers-through"
+ rank: "invariant-only"
+ - from: "capability.ask-user-question-auto-resolution"
+ to: "evidence.conversation-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.subagents"
+ to: "state.subagent-child-runtime"
+ type: "executes-with"
+ rank: "must-inspect"
+ condition: "Agent tool input does not select a model; the resolved Agent profile is the only child-model authority, with parent-model inheritance when profile.model is absent."
+ - from: "capability.subagents"
+ to: "surface.subagent-session-detail"
+ type: "renders-child-owned-conversation-in"
+ rank: "must-inspect"
+ - from: "surface.subagent-session-detail"
+ to: "state.conversation-projection"
+ type: "projects-from"
+ rank: "must-inspect"
+ condition: "Main and child details share SessionPane and ConversationTimeline; read-only child details may display provider retry and durable initial-model facts without enabling composer or row actions."
+ - from: "state.subagent-child-runtime"
+ to: "persistence.session-model-timeline"
+ type: "persists-initial-effective-model-to"
+ rank: "must-inspect"
+ condition: "Only a fresh child records model_change with absent source before its first input; resumed and pre-existing children are not backfilled."
+ - from: "persistence.session-model-timeline"
+ to: "state.conversation-projection"
+ type: "recovers-model-boundaries-through"
+ rank: "must-inspect"
+ condition: "A source-less model_change synthesizes previousModelRef=null as a generic empty-to-model boundary; ordinary A-to-B model changes keep complete source identity."
+ - from: "surface.subagent-session-detail"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "surface.subagent-session-detail"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "capability.subagents"
+ to: "surface.parent-task-blocking-interaction"
+ type: "renders-blocking-interactions-in"
+ rank: "must-inspect"
+ - from: "state.subagent-child-runtime"
+ to: "service.subagent-interaction-broker"
+ type: "delegates-blocking-interactions-through"
+ rank: "must-inspect"
+ - from: "state.subagent-child-runtime"
+ to: "state.conversation-projection"
+ type: "mirrors-blocking-interaction-events-to"
+ rank: "must-inspect"
+ condition: "PermissionRequested/Resolved/Denied events use the parent session while preserving the child origin and stable mirrored tool identity."
+ - from: "service.subagent-interaction-broker"
+ to: "state.conversation-projection"
+ type: "shares-parent-request-identity-with"
+ rank: "must-inspect"
+ condition: "The broker response route and parent projection share the unchanged requestId; the broker alone does not create pendingInteractions."
+ - from: "state.conversation-projection"
+ to: "surface.parent-task-blocking-interaction"
+ type: "drives"
+ rank: "must-inspect"
+ - from: "capability.conversation-runtime"
+ to: "surface.conversation-tool-call"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.conversation-tool-call"
+ to: "state.conversation-projection"
+ type: "projects-from"
+ rank: "must-inspect"
+ - from: "surface.conversation-tool-call"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "surface.conversation-tool-call"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "state.subagent-child-runtime"
+ to: "state.conversation-projection"
+ type: "must-not-persist-raw-events-to"
+ rank: "invariant-only"
+ condition: "Raw child events and model-io remain child-owned; only parent-routed live interaction/tool mirrors enter the parent projection."
+ - from: "surface.conversation-query-edit"
+ to: "service.conversation-row-actions"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "service.conversation-row-actions"
+ to: "state.conversation-projection"
+ type: "resolves-target-from"
+ rank: "must-inspect"
+ condition: "canEdit and editUserQuery must share the same canonical edit target authority at one projection revision."
+ - from: "state.conversation-projection"
+ to: "capability.conversation-runtime"
+ type: "owns-runtime-for"
+ rank: "must-inspect"
+ - from: "state.conversation-projection"
+ to: "evidence.conversation-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+ condition: "A12 covers the normal sendText path: timely authoritative row/queue settles without recovery; complete topic silence triggers one same-sub resync and never replays the command."
+ - from: "state.conversation-projection"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ condition: "Accepted-input projection watchdog reuses same-sub recovery and never switches desktop continuous to replayable delivery."
+ - from: "state.conversation-projection"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ condition: "Accepted-input projection watchdog reuses the existing replayable same-sub recovery; it does not create a host-local queue or bypass snapshot/gap recovery."
+ - from: "capability.conversation-query-navigation"
+ to: "surface.conversation-query-navigator"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.conversation-query-navigator"
+ to: "state.conversation-projection"
+ type: "projects-from"
+ rank: "must-inspect"
+ - from: "surface.conversation-query-navigator"
+ to: "service.conversation-rows-range"
+ type: "hydrates-through"
+ rank: "must-inspect"
+ condition: "Only while the pane-wide navigator passes its layout gate; narrow mobile does not hydrate full history for a hidden rail."
+ - from: "service.conversation-rows-range"
+ to: "state.conversation-projection"
+ type: "reads-authoritative-history-from"
+ rank: "must-inspect"
+ - from: "surface.conversation-query-navigator"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "surface.conversation-query-navigator"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+
+ - from: "capability.task-persistence"
+ to: "service.session-runtime-activation"
+ type: "restores-through"
+ rank: "must-inspect"
+ - from: "capability.workspace-identity"
+ to: "service.persisted-remote-session-repair"
+ type: "repairs-legacy-state-through"
+ rank: "must-inspect"
+ condition: "Only task-allowlisted null identities or deterministic historical remote path shapes may be repaired; ambiguous path-only ownership stays fail-closed."
+ - from: "service.persisted-remote-session-repair"
+ to: "service.session-runtime-activation"
+ type: "repairs-before-materialization"
+ rank: "must-inspect"
+ - from: "service.persisted-remote-session-repair"
+ to: "boundary.workspace-key"
+ type: "preserves-isolation"
+ rank: "must-inspect"
+ - from: "service.session-runtime-activation"
+ to: "state.conversation-projection"
+ type: "activates-before-hydration"
+ rank: "must-inspect"
+ condition: "V4 cold subscribe preserves runtime/config/workspace/restore-warning side effects but must not materialize a legacy full session snapshot. One gateway READY promise covers activation and projection hydration; query and command wait before reading current facts."
+ - from: "service.session-runtime-activation"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "service.session-runtime-activation"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "service.session-runtime-activation"
+ to: "boundary.workspace-key"
+ type: "preserves-isolation"
+ rank: "must-inspect"
+ - from: "capability.agent-core-runtime"
+ to: "capability.session-residency-management"
+ type: "provides-authoritative-work-guards-to"
+ rank: "must-inspect"
+ condition: "Resident eviction must use the runtime-owned blocking-work aggregate (active/queued/runtime-drain, task registry, detached sidecars, memory work) plus protocol finalization leases; activeAbortController alone is insufficient."
+ - from: "capability.session-residency-management"
+ to: "capability.conversation-runtime"
+ type: "bounds-resident-materialization-of"
+ rank: "must-inspect"
+ condition: "Each CLI targets a bounded number of resident sessions but may exceed the target while sessions are protected; cross-session concurrency remains unlimited."
+ - from: "capability.session-residency-management"
+ to: "service.session-runtime-activation"
+ type: "reactivates-through"
+ rank: "must-inspect"
+ condition: "Deactivated sessions are indistinguishable from never-loaded persisted sessions and reuse the existing cold-resume path."
+ - from: "capability.session-residency-management"
+ to: "state.conversation-projection"
+ type: "coordinates-lifecycle-through-subscription"
+ rank: "should-inspect"
+ condition: "Renderer projection stores release their own pane leases and unsubscribe before CLI eviction becomes eligible; no deactivation notification is sent."
+ - from: "capability.session-residency-management"
+ to: "persistence.task-index"
+ type: "must-not-remove-from"
+ rank: "invariant-only"
+ condition: "Resident eviction releases runtime memory only and never emits session.removed or changes task-list membership."
+ - from: "capability.session-residency-management"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ condition: "An active desktop conversation subscription protects its resident session."
+ - from: "capability.session-residency-management"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery"
+ rank: "invariant-only"
+ condition: "A V4 replayable subscription protects its session; the current legacy session/subscribe stream has no unsubscribe and remains conservatively protected. Existing snapshot/gap/cold-resume boundaries do not change."
+ - from: "capability.session-residency-management"
+ to: "boundary.workspace-key"
+ type: "must-preserve-isolation"
+ rank: "invariant-only"
+ condition: "The pool is process-local and session-addressed; remote workspace routing keeps workspaceIdentity?.trim() || workspacePath."
+ - from: "capability.session-residency-management"
+ to: "evidence.conversation-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "surface.automation-edit"
+ to: "state.automation-form-draft"
+ type: "owns-draft"
+ rank: "must-inspect"
+ - from: "surface.automation-template-gallery"
+ to: "shared-ui.automation-template-catalog"
+ type: "options-from"
+ rank: "must-inspect"
+ - from: "surface.off-peak-home-template-carousel"
+ to: "shared-ui.automation-template-catalog"
+ type: "options-from"
+ rank: "must-inspect"
+ condition: "The home surface keeps the first two ordinary remote templates plus local Customize, clamps descriptions to two lines, and resolves each locale from ClientSceneItem.descs before falling back to contents; contents remains the draft prompt source."
+ - from: "shared-ui.automation-template-catalog"
+ to: "service.client-scenes"
+ type: "loads-from"
+ rank: "must-inspect"
+ condition: "scheduled-task and off-peak-task are candidate-only remote configuration; blank-title items are rejected; prompt item img is a Lucide canonical name shared by the New task home cards and Automations gallery, imgs is ignored, and unknown names preserve each surface's local Lucide fallback; scheduled cron must round-trip through the Automation editor without loss; pending Automations galleries show four skeleton cards per visible section and settled empty catalogs show a localized unavailable state; the home carousel alone appends one local Customize fallback when remote data omits it."
+ - from: "surface.automation-template-gallery"
+ to: "state.automation-form-draft"
+ type: "prefills"
+ rank: "must-inspect"
+ condition: "Click materializes current-locale values into a normal local draft and never creates or sends automatically."
+ - from: "surface.off-peak-home-template-carousel"
+ to: "surface.off-peak-creation"
+ type: "prefills"
+ rank: "must-inspect"
+ condition: "Click freezes current-locale labels and contents before navigation; display-only descs must not replace the contents-backed draft prompt, and later locale or Client Scenes refresh cannot overwrite user edits."
+ - from: "shared-ui.automation-template-catalog"
+ to: "persistence.automation-record"
+ type: "must-not-persist-to"
+ rank: "invariant-only"
+ condition: "Only user-confirmed final form values persist; Client Scenes scene, item, defaults, and labels metadata remain candidate-only."
+ - from: "state.automation-form-draft"
+ to: "service.automation"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "capability.automations"
+ to: "surface.conversation-automation-tools"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.conversation-automation-tools"
+ to: "service.automation"
+ type: "commits-to"
+ rank: "must-inspect"
+ condition: "Agent tool writes must remain scoped to the current workspace and preserve automation identity and run history."
+ - from: "service.automation"
+ to: "persistence.automation-record"
+ type: "persists-to"
+ rank: "must-inspect"
+ - from: "persistence.automation-record"
+ to: "capability.automations"
+ type: "owns-config-for"
+ rank: "must-inspect"
+ - from: "surface.user-subagent-form"
+ to: "state.subagent-form-draft"
+ type: "owns-draft"
+ rank: "must-inspect"
+ - from: "state.subagent-form-draft"
+ to: "service.subagents"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "surface.builtin-subagent-model"
+ to: "service.subagents"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "service.subagents"
+ to: "persistence.subagent-markdown"
+ type: "persists-to"
+ rank: "must-inspect"
+ condition: "Each Environment persists its own user, workspace, and built-in Subagent configuration; current Settings writes Local Host configuration only and does not synchronize to remote Environments."
+ - from: "persistence.subagent-markdown"
+ to: "capability.subagents"
+ type: "owns-config-for"
+ rank: "must-inspect"
+ - from: "capability.oauth-login-attribution"
+ to: "service.oauth"
+ type: "handled-by"
+ rank: "must-inspect"
+ - from: "service.oauth"
+ to: "state.oauth-pending-callback"
+ type: "owns-lifecycle"
+ rank: "must-inspect"
+ - from: "service.oauth"
+ to: "persistence.oauth-login-attribution"
+ type: "persists-to"
+ rank: "must-inspect"
+ - from: "service.telemetry-core"
+ to: "persistence.oauth-login-attribution"
+ type: "reads-best-effort"
+ rank: "should-inspect"
+ condition: "Attribution credential failures must not block telemetry delivery."
+
+ - from: "surface.automation-edit"
+ to: "state.conversation-projection"
+ type: "must-not-mutate"
+ rank: "invariant-only"
+ condition: "Opening, editing, or cancelling an automation must not change the current workspace or conversation model."
+
+ - from: "capability.off-peak-telemetry"
+ to: "surface.off-peak-creation"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.off-peak-telemetry"
+ to: "surface.off-peak-upgrade"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.off-peak-upgrade"
+ to: "state.coding-plan-purchase-funnel"
+ type: "starts-funnel-with"
+ rank: "must-inspect"
+ condition: "Both idle-time Upgrade entries use app.session/session_idle_time and one purchase_funnel_id."
+ - from: "state.coding-plan-purchase-funnel"
+ to: "state.oauth-pending-callback"
+ type: "must-survive-auth-rerender"
+ rank: "must-inspect"
+ condition: "OAuth refresh may update credentials and products but must preserve purchase_funnel_id, upgrade_source, and entry plan state."
+ - from: "state.coding-plan-purchase-funnel"
+ to: "service.telemetry-core"
+ type: "reports-through-existing-route"
+ rank: "must-inspect"
+ condition: "Upgrade, product, cycle, pay, and result events reuse the same funnel context."
+ - from: "surface.off-peak-creation"
+ to: "service.off-peak-task"
+ type: "commits-to"
+ rank: "must-inspect"
+ condition: "Creation returns a structured business result before best-effort telemetry and never exposes raw errors."
+ - from: "service.off-peak-task"
+ to: "capability.off-peak-model-execution"
+ type: "dispatches"
+ rank: "must-inspect"
+ condition: "The scheduler supplies a standard idle ModelSelection plus request auth, ticket, attribution, and optional child override; it does not construct a Provider snapshot after M4."
+ - from: "service.off-peak-task"
+ to: "capability.provider-registry"
+ type: "reads-current-eligibility"
+ rank: "must-inspect"
+ condition: "start only waits for readiness; Host credentials use the latest completed snapshot and Registry View revisions invalidate the shared UI eligibility check."
+ - from: "capability.off-peak-model-execution"
+ to: "capability.provider-registry"
+ type: "creates-model-through"
+ rank: "must-inspect"
+ condition: "The complete hidden idle Provider may be preflighted through the ordinary Registry before dispatch and is finally validated when the ordinary ModelFactory builds the Model; no execution Registry or temporary Provider source exists."
+ - from: "capability.off-peak-model-execution"
+ to: "capability.model-capabilities"
+ type: "reads-effective-model-config-from"
+ rank: "must-inspect"
+ condition: "Context, output limits, input/output formats, options, and reasoning mappings come from Built-in Model Config Rules rather than Host inference."
+ - from: "capability.off-peak-model-execution"
+ to: "capability.subagents"
+ type: "overrides-foreground-child-selection-for"
+ rank: "must-inspect"
+ condition: "Each foreground child creates its own Model with the idle selection and request auth; background children fail closed before requesting a model."
+ - from: "capability.off-peak-model-execution"
+ to: "state.off-peak-turn-attribution"
+ type: "attributes-actual-model-through"
+ rank: "must-inspect"
+ condition: "Usage, trace, and errors retain the actual idle providerId/modelId while offPeakTaskId and ticket remain execution dimensions."
+ - from: "capability.off-peak-model-execution"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "capability.off-peak-model-execution"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ condition: "The narrow execution context does not change ordinary Web/SSH replayable session state."
+ - from: "service.off-peak-task"
+ to: "state.off-peak-turn-attribution"
+ type: "dispatches-with-stable-identity"
+ rank: "must-inspect"
+ condition: "Every automatic turn has a distinct message identity while preserving the stable offPeakTaskId across one or more talks."
+ - from: "state.off-peak-turn-attribution"
+ to: "service.telemetry-core"
+ type: "reports-through-existing-route"
+ rank: "must-inspect"
+ condition: "Desktop uses the active CCbuddy business telemetry route; Web remains no-op and no reliable outbox is introduced."
+ - from: "state.off-peak-turn-attribution"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery-semantics"
+ rank: "invariant-only"
+ - from: "state.off-peak-turn-attribution"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery-semantics"
+ rank: "invariant-only"
+ - from: "state.subagent-form-draft"
+ to: "state.conversation-projection"
+ type: "must-remain-isolated-from"
+ rank: "invariant-only"
+
+ - from: "capability.conversation-runtime"
+ to: "boundary.desktop-continuous"
+ type: "delivered-by"
+ rank: "conditional"
+ condition: "Desktop local main path."
- from: capability.conversation-runtime
- to: service.ccbuddy-agent-connection-scope
- type: attaches-through
- rank: must-inspect
- - from: service.conversation-topic-publisher
- to: boundary.desktop-continuous
- type: uses-delivery-policy
- rank: conditional
- condition: For Desktop continuous attachments.
- - from: service.ccbuddy-agent-connection-scope
- to: boundary.desktop-continuous
- type: injects-trusted-client-mode
- rank: conditional
- condition: For Desktop continuous attachments.
- - from: service.conversation-topic-publisher
to: boundary.web-remote-replayable
- type: uses-delivery-policy
+ type: recovers-via
rank: conditional
- condition: For replayable Web remote and mobile attachments.
- - from: service.ccbuddy-agent-connection-scope
+ condition: Ordinary Web and SSH direct server connections.
+ - from: "boundary.web-remote-replayable"
+ to: "boundary.desktop-continuous"
+ type: "must-remain-isolated-from"
+ rank: "invariant-only"
+
+ - from: "capability.workspace-identity"
+ to: "service.workspace-identity-builder"
+ type: "implemented-by"
+ rank: "must-inspect"
+ - from: "capability.remote-file-actions"
+ to: "surface.git-review-file-actions"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.remote-file-actions"
+ to: "surface.project-task-list"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.git-review-file-actions"
+ to: "shared-ui.remote-file-context-actions"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "surface.project-task-list"
+ to: "shared-ui.remote-file-context-actions"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "shared-ui.remote-file-context-actions"
+ to: "capability.workspace-identity"
+ type: "resolves-target-with"
+ rank: "must-inspect"
+ condition: "Remote file actions must match workspaceIdentity and remoteSessionId before passing a sanitized WSL or SSH target to the platform boundary."
+
+ - from: "capability.prompt-attachment-materialization"
+ to: "surface.conversation-composer-attachments"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.conversation-composer-attachments"
+ to: "service.prompt-attachment-transfer"
+ type: "commits-to"
+ rank: "must-inspect"
+ condition: "Host-local attachments with any non-empty workspaceIdentity, including identities unknown to the current parser, must wait for remoteSessionId and stage before send admission."
+ - from: "capability.prompt-attachment-materialization"
+ to: "boundary.workspace-key"
+ type: "isolated-by"
+ rank: "must-inspect"
+ - from: "capability.video-input"
+ to: "capability.prompt-attachment-materialization"
+ type: "uses"
+ rank: "must-inspect"
+ - from: "capability.video-input"
+ to: "capability.model-capabilities"
+ type: "validates-effective-capability-with"
+ rank: "must-inspect"
+ condition: "Provider-visible video requires both model support and a provider adapter wire format that can encode video."
+ - from: "capability.video-input"
+ to: "persistence.session-media-artifacts"
+ type: "persists-provider-neutral-source-to"
+ rank: "must-inspect"
+ condition: "Session messages and completed tool parts persist artifact references and media metadata; the current provider base64 wire payload is derived at request time rather than stored as the sole fact."
+ - from: "capability.video-input"
+ to: "surface.conversation-sent-media-preview"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "Sent image/video attachments in one user message share an ordered gallery with wrapping previous/next and keyboard navigation; composer and sent grid video thumbnails share a centered translucent blurred circular play affordance; visible mounted rows resolve exact-row video preview sources artifact-first, with Desktop local workspaces playing artifact/local_ref paths only after Host asks Main to authorize their canonical paths, while remote or unavailable-path cases use bounded chunked Blob URLs; image-only transform/download controls stay isolated from native video playback, and decode/read failure changes only the active preview item rather than input semantics."
+ - from: "capability.video-input"
+ to: "surface.conversation-composer-attachments"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "Composer image/video attachments with local object URLs share the same ordered media gallery and starting-index behavior; preview navigation must not mutate upload, removal, or send state."
+ - from: "surface.conversation-sent-media-preview"
+ to: "persistence.session-media-artifacts"
+ type: "reads-exact-row-artifact-from"
+ rank: "must-inspect"
+ condition: "The CLI authorizes row identity and attachment index. Desktop local preview prefers an artifact-derived .ccbuddy media path, uses an authorized metadata-only local_ref only when no artifact exists, and Host registers the selected realpath with Main before renderer playback; arbitrary absolute paths in renderer URLs remain denied. Artifact path materialization failure still falls back only to chunked artifact bytes."
+ - from: surface.conversation-sent-media-preview
to: boundary.web-remote-replayable
- type: injects-trusted-client-mode
- rank: conditional
- condition: For replayable Web remote and mobile attachments.
- - from: boundary.web-remote-replayable
- to: boundary.desktop-continuous
- type: must-remain-isolated-from
+ type: must-not-change-recovery-semantics
rank: invariant-only
- - from: state.conversation-projection
- to: service.conversation-command-inbox
- type: must-not-duplicate-admission-of
+ condition: Only Desktop local workspaces request local media paths. Web and remote workspaces retain the attached-host chunk query; relay and main do not own session/replay state or start another runtime.
+ - from: "persistence.session-media-artifacts"
+ to: "capability.model-selection"
+ type: "rehydrates-for-current-provider"
+ rank: "must-inspect"
+ condition: "In-conversation model switches and cold resume rebuild media from durable artifacts, then derive the current provider base64 request shape."
+ - from: persistence.session-media-artifacts
+ to: boundary.web-remote-replayable
+ type: must-not-change-recovery-semantics
rank: invariant-only
- condition:
- The client may display pending optimistic commands; accepted input order and idempotency
- remain CLI-owned.
- - from: capability.workspace-identity
- to: service.workspace-identity-builder
- type: implemented-by
- rank: must-inspect
- - from: service.workspace-identity-builder
- to: boundary.workspace-key
- type: supplies-identity-for
- rank: must-inspect
- - from: service.ccbuddy-agent-connection-scope
- to: boundary.workspace-key
- type: isolated-by
- rank: must-inspect
- - from: persistence.task-index
- to: boundary.workspace-key
- type: isolated-by
- rank: must-inspect
- - from: persistence.task-index
- to: state.conversation-product-projection
- type: must-not-replace
+ condition: Media artifacts remain shared-host session facts; relay and Web/SSH replayable state do not become binary owners or start a second runtime.
+ - from: capability.video-input
+ to: boundary.web-remote-replayable
+ type: must-not-change-recovery-semantics
rank: invariant-only
- condition: Task index metadata is distinct from canonical conversation content and runtime state.
- - from: capability.composer-trigger-routing
- to: surface.composer-at-context-picker
- type: renders-in
- rank: must-inspect
- - from: capability.composer-trigger-routing
- to: surface.composer-slash-capability-picker
- type: renders-in
+ condition: Video payload support does not move task, stream, queue, or snapshot ownership into the Web/SSH replayable boundary.
+ - from: "service.workspace-identity-builder"
+ to: "boundary.workspace-key"
+ type: "produces"
+ rank: "must-inspect"
+ - from: "capability.remote-delivery"
+ to: "boundary.workspace-key"
+ type: "isolated-by"
+ rank: "conditional"
+ - from: "capability.task-persistence"
+ to: "persistence.task-index"
+ type: "indexed-by"
+ rank: "must-inspect"
+ - from: "capability.task-persistence"
+ to: "surface.project-task-list"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.task-title-overflow-marquee"
+ to: "shared-ui.task-title-overflow-text"
+ type: "implemented-by"
+ rank: "must-inspect"
+ - from: "capability.task-title-overflow-marquee"
+ to: "surface.project-task-list"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.task-title-overflow-marquee"
+ to: "surface.grouped-task-list"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.task-title-overflow-marquee"
+ to: "surface.pinned-task-list"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "shared-ui.task-title-overflow-text"
+ to: "state.task-list-row-projection"
+ type: "renders-title-from"
+ rank: "should-inspect"
+ condition: "Presentation must not mutate membership, navigation, activity, or workspace identity."
+ - from: "capability.task-persistence"
+ to: "surface.grouped-task-list"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.task-persistence"
+ to: "surface.pinned-task-list"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.project-task-list"
+ to: "state.task-list-row-projection"
+ type: "projects-from"
+ rank: "must-inspect"
+ - from: "surface.grouped-task-list"
+ to: "state.task-list-row-projection"
+ type: "projects-from"
+ rank: "must-inspect"
+ - from: "surface.pinned-task-list"
+ to: "state.task-list-row-projection"
+ type: "projects-from"
+ rank: "must-inspect"
+ - from: "surface.grouped-task-list"
+ to: "surface.pinned-task-list"
+ type: "renders-alongside"
+ rank: "must-inspect"
+ condition: "Desktop Group view renders the global pinned section above the grouped body; pinned tasks are excluded from the grouped body so each task appears once."
+ - from: "state.task-list-row-projection"
+ to: "service.ccbuddy-task-list"
+ type: "queries-through"
+ rank: "must-inspect"
+ - from: "capability.conversation-runtime"
+ to: "service.ccbuddy-task-index-syncer"
+ type: "publishes-visible-task-summary-to"
+ rank: "must-inspect"
+ condition: "A persisted fork child must publish its hydrated non-draft summary without waiting for another runtime event or process restart."
+ - from: "service.ccbuddy-task-index-syncer"
+ to: "persistence.task-index"
+ type: "persists-visible-task-row-to"
+ rank: "must-inspect"
+ condition: "The first non-draft summary creates exactly one task row and emits task_created for task-list cache invalidation."
+ - from: "service.ccbuddy-task-list"
+ to: "persistence.task-index"
+ type: "reads-writes"
+ rank: "must-inspect"
+ - from: "persistence.task-index"
+ to: "boundary.workspace-key"
+ type: "isolated-by"
+ rank: "must-inspect"
+ condition: "Task-index reads keep SQLite entity coordinates authoritative over stale meta_json and recover legacy remote identity only from a valid remote workspace_key."
+ - from: "state.task-list-row-projection"
+ to: "state.conversation-projection"
+ type: "enriched-by"
+ rank: "should-inspect"
+ condition: "Conversation and sessions-index facts enrich activity/detail but do not own row membership."
+ - from: "state.task-list-row-projection"
+ to: "boundary.workspace-key"
+ type: "isolated-by"
+ rank: "must-inspect"
+ - from: "persistence.task-index"
+ to: "state.conversation-projection"
+ type: "must-not-authorize"
+ rank: "invariant-only"
+ condition: "Task index metadata is not the authority for session content or current runtime settings."
+
+ - from: "capability.mcp-async-tool-catalog"
+ to: "state.mcp-tool-catalog-cache"
+ type: "cached-by"
+ rank: "must-inspect"
+ - from: "capability.mcp-async-tool-catalog"
+ to: "service.mcp-session-runtime"
+ type: "executes-through"
+ rank: "must-inspect"
+ condition: "Cached catalog entries advertise tools only; every invocation must wait for and resolve against the current session live MCP runtime, and stale issued contracts remain resolvable through provider-invisible tombstones."
+ - from: "state.mcp-tool-catalog-cache"
+ to: "service.mcp-session-runtime"
+ type: "advertises-before-live-readiness-of"
+ rank: "must-inspect"
+ condition: "A complete fresh stdio catalog may unblock provider sampling while the independent session-scoped live runtime is still starting."
+ - from: "service.mcp-session-runtime"
+ to: "capability.conversation-runtime"
+ type: "provides-tools-to"
+ rank: "must-inspect"
+ - from: "surface.customize-capability-settings"
+ to: "state.mcp-tool-catalog-cache"
+ type: "must-not-project-readiness-from"
+ rank: "invariant-only"
+ condition: "MCP settings status remains a live control-plane fact; a catalog hit must never render as connected."
+ - from: "state.mcp-tool-catalog-cache"
+ to: "boundary.workspace-key"
+ type: "isolated-by"
+ rank: "must-inspect"
+ condition: "Catalog identity uses workspaceIdentity?.trim() || workspacePath; Phase 2 keeps every remote lookup unsupported, and remote cache may be enabled only after real workspace and remote-session identity reach the runtime."
+ - from: "capability.mcp-async-tool-catalog"
+ to: "capability.subagents"
+ type: "must-preserve-live-borrowing-for"
+ rank: "invariant-only"
+ condition: "Subagents inherit the parent connected, scope-filtered live snapshot and never gain execution authority from advertisement cache."
+ - from: "capability.mcp-async-tool-catalog"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.mcp-async-tool-catalog"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.mcp-async-tool-catalog"
+ to: "evidence.mcp-runtime-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.conversation-runtime"
+ to: "evidence.conversation-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.composer-trigger-routing"
+ to: "surface.composer-at-context-picker"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "The @ primary groups are ordered Plugins, files, sessions, then retained Whiteboards; its sessions group remains current-workspace only. # remains a compatible sessions trigger and aggregates opened/restored workspace scopes that share the current Agent service/session-store authority, while foreign or disconnected remote authorities stay excluded."
+ - from: "capability.composer-trigger-routing"
+ to: "surface.composer-slash-capability-picker"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "The slash groups remain commands, Skills, then Subagents; $ and Yen aliases remain compatible Skills triggers."
+ - from: "capability.composer-trigger-routing"
+ to: "surface.composer-skill-compatibility-picker"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "$ and Yen aliases route to the same provider-filtered Skill catalog as the slash Skills group without changing canonical Skill mention Markdown."
+ - from: "capability.skill-catalog-discovery"
+ to: "service.workspace-skills-catalog"
+ type: "served-by"
+ rank: "must-inspect"
+ - from: "service.workspace-skills-catalog"
+ to: "persistence.skill-roots"
+ type: "reads-from"
+ rank: "must-inspect"
+ condition: "At every workspace ancestor through the worktree root, .ccbuddy/skills and .agents/skills are merged; the presence of one root must not suppress the other. User and Plugin roots retain their existing discovery contracts."
+ - from: "capability.skill-catalog-discovery"
+ to: "capability.agent-core-runtime"
+ type: "must-match-discovery-roots-of"
+ rank: "must-inspect"
+ condition: "Host/UI catalog discovery and Agent runtime discovery merge the same workspace .ccbuddy/skills and .agents/skills roots so visible references match executable capabilities."
+ - from: "capability.skill-catalog-discovery"
+ to: "surface.customize-capability-settings"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.skill-catalog-discovery"
+ to: "surface.composer-slash-capability-picker"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.skill-catalog-discovery"
+ to: "surface.composer-skill-compatibility-picker"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.skill-catalog-discovery"
+ to: "evidence.skill-catalog-discovery-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.plan-slash-shortcut"
+ to: "surface.composer-slash-capability-picker"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "The app protocol catalog exposes /plan [task] for desktop, Web, and /remote Composer surfaces; selection inserts the command token and does not execute it."
+ - from: "capability.plan-slash-shortcut"
+ to: "state.conversation-projection"
+ type: "commits-through"
+ rank: "must-inspect"
+ condition: "A task-bearing shortcut commits the existing mode CAS before ordinary sendText; an empty shortcut only changes mode and creates no user/provider turn."
+ - from: "capability.plan-slash-shortcut"
+ to: "capability.conversation-runtime"
+ type: "depends-on"
+ rank: "must-inspect"
+ condition: "The shortcut is syntactic sugar over switchCollaborationMode, createSession config.mode, and sendText; it adds no wire command, queue, or persistence authority."
+ - from: "capability.plan-slash-shortcut"
+ to: "boundary.workspace-key"
+ type: "preserves-execution-scope"
+ rank: "invariant-only"
+ condition: "Workspace identity continues to use workspaceIdentity?.trim() || workspacePath, with remoteSessionId carried by existing session routing."
+ - from: "capability.plan-slash-shortcut"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.plan-slash-shortcut"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.plan-slash-shortcut"
+ to: "evidence.conversation-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.selection-side-chat"
+ to: "surface.conversation-selection-side-slash"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "App-local /side and /btw remain available for created desktop parent sessions, including running parents; draft, selection-side-chat, read-only, and mobile viewport surfaces remain hidden. CLI same-name commands win."
+ - from: "surface.conversation-selection-side-slash"
+ to: "state.conversation-projection"
+ type: "validates-with"
+ rank: "must-inspect"
+ condition: "The submit path consumes only text-only arguments and rechecks the current parent session before dispatch; attachments and structured composer contexts remain on the normal parent send path."
+ - from: "surface.conversation-selection-side-slash"
+ to: "service.selection-side-session-creation"
+ type: "commits-to"
+ rank: "must-inspect"
+ condition: "A bare selected command removes its active token and creates an empty child; an argument-bearing submit dispatches createSelectionSideSession with firstInput text and never sends that text to the parent."
+ - from: "service.selection-side-session-creation"
+ to: "capability.conversation-runtime"
+ type: "creates-child-in"
+ rank: "must-inspect"
+ - from: "capability.selection-side-chat"
+ to: "boundary.web-remote-replayable"
+ type: "must-remain-hidden-from-mobile-viewport"
+ rank: "invariant-only"
+ - from: "capability.selection-side-chat"
+ to: "evidence.conversation-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.session-skill-catalog"
+ to: "surface.composer-slash-capability-picker"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "Slash Skills and the compatible $, ¥, and ¥ triggers share one catalog authority and retain the existing canonical Skill mention."
+ - from: "capability.session-skill-catalog"
+ to: "state.session-skill-catalog"
+ type: "state-owned-by"
+ rank: "must-inspect"
+ condition: "A resident Session reads the Skill outcome frozen by its AgentRuntime context initialization; cold resume creates a new runtime and catalog."
+ - from: "capability.session-skill-catalog"
+ to: "service.skill-reference-catalog"
+ type: "served-by"
+ rank: "must-inspect"
+ condition: "A draft without prewarm reads current workspace discovery; once prewarm is ready its prewarmSessionId selects that runtime's frozen catalog. Formal Session requests use sessionId and fail closed when the runtime is absent."
+ - from: "capability.session-skill-catalog"
+ to: "capability.conversation-runtime"
+ type: "depends-on"
+ rank: "should-inspect"
+ condition: "Runtime restart advances the Picker request identity and discards results from the previous runtime without changing conversation delivery semantics."
+ - from: "capability.session-skill-catalog"
+ to: "boundary.workspace-key"
+ type: "depends-on"
+ rank: "invariant-only"
+ condition: "Catalog isolation uses workspaceIdentity fallback plus remoteSessionId; workspacePath remains the filesystem discovery target."
+ - from: "capability.session-skill-catalog"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.session-skill-catalog"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.session-skill-catalog"
+ to: "evidence.conversation-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.composer-trigger-routing"
+ to: "capability.conversation-runtime"
+ type: "must-not-change-canonical-input-for"
+ rank: "invariant-only"
+ condition: "Discovery triggers may change grouping, but selected items retain their existing plugin, file, session, Whiteboard, command, Skill, and Subagent canonical Markdown."
+ - from: "capability.composer-trigger-routing"
+ to: "evidence.conversation-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.plugin-source-acquisition"
+ to: "service.plugin-source-materializer"
+ type: "served-by"
+ rank: "must-inspect"
+ condition: "Public github.com HTTPS sources use bounded secure Archive extraction; private, SSH, non-GitHub and advanced Git semantics retain the system Git path."
+ - from: "capability.plugin-source-acquisition"
+ to: "persistence.plugin-marketplace-installed-cache"
+ type: "persists-to"
+ rank: "must-inspect"
+ condition: "Validated Marketplace snapshots and installed plugin trees activate through same-volume staging, backup, rename and rollback; refresh failure preserves the last successful snapshot."
+ - from: "capability.plugin-source-acquisition"
+ to: "boundary.plugin-agent-host"
+ type: "executes-through"
+ rank: "must-inspect"
+ condition: "Desktop local, standalone CLI and SSH/WSL/Docker workspace operations all execute in the authoritative workspace Agent Host; bundling Git in Desktop would not satisfy remote hosts."
+ - from: "persistence.plugin-marketplace-installed-cache"
+ to: "capability.plugin-reference"
+ type: "read-by"
+ rank: "invariant-only"
+ condition: "Startup discovery, enable state and session injection remain offline cache reads and never trigger Git or HTTP source refresh."
+ - from: "capability.plugin-source-acquisition"
+ to: "evidence.plugin-source-acquisition-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.plugin-source-acquisition"
+ to: "capability.workspace-identity"
+ type: "constrained-by"
+ rank: "invariant-only"
+ condition: "Remote workspace authority continues to use workspaceIdentity and remoteSessionId; source acquisition does not introduce path-only identity keys."
+
+ - from: "capability.plugin-catalog-runtime-separation"
+ to: "persistence.plugin-marketplace-installed-cache"
+ type: "separates-catalog-from-runtime"
+ rank: "must-inspect"
+ condition: "Bundled Catalog and immutable official cache remain readable after builtin uninstall; cache presence never implies Runtime discovery or enablement."
+ - from: "capability.plugin-catalog-runtime-separation"
+ to: "state.plugin-runtime-suppression"
+ type: "must-preserve-runtime-contract"
+ rank: "must-inspect"
+ condition: "suppressedBuiltins is the only builtin Runtime suppression truth; Catalog and describe never mutate it."
+ - from: "state.plugin-runtime-suppression"
+ to: "service.plugin-catalog-detail"
+ type: "must-not-mutate"
+ rank: "invariant-only"
+ condition: "plugins/describe is read-only for suppressed builtins and does not restore, enable, or create an installed record."
+ - from: "service.plugin-catalog-detail"
+ to: "persistence.plugin-marketplace-installed-cache"
+ type: "reads-catalog-source-through"
+ rank: "must-inspect"
+ condition: "filesystem/SEA is normalized at the seed boundary; adapters consume the materialized cachePath for component enumeration."
+ - from: "capability.plugin-catalog-runtime-separation"
+ to: "boundary.plugin-agent-host"
+ type: "executes-through"
+ rank: "must-inspect"
+ condition: "Local and remote Agent Host authority remains unchanged; no second runtime is created for detail or restore."
+ - from: "capability.plugin-catalog-runtime-separation"
+ to: "evidence.plugin-catalog-runtime-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.workspace-project-plugin-scope"
+ to: "surface.workspace-plugin-scope-controls"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "User and Workspace are configuration views over Host-available Plugins, not mutually exclusive ownership lists. User reads User/default state; Workspace reads effective merged state. Marketplace has no config scope, installs to User, and returns to the concrete origin scopeKey."
+ - from: "surface.workspace-plugin-scope-controls"
+ to: "capability.workspace-identity"
+ type: "preserves-return-scope-through"
+ rank: "must-inspect"
+ condition: "Settings -> Marketplace -> Settings navigation carries returnScopeKey=workspaceIdentity?.trim() || workspacePath; missing or disconnected targets fall back to User."
+ - from: "capability.workspace-project-plugin-scope"
+ to: "persistence.workspace-plugin-project-config"
+ type: "persists-shared-declaration-to"
+ rank: "must-inspect"
+ condition: "Tracked .ccbuddy/config.json owns project enabledPlugins, extraKnownMarketplaces and plaintext options/credentials/secrets; it never owns package bytes or Plugin data."
+ - from: "persistence.workspace-plugin-project-config"
+ to: "state.workspace-plugin-effective-config"
+ type: "merges-into"
+ rank: "must-inspect"
+ condition: "Plugin enabled maps merge per Plugin ID; options, including sensitive values, merge per Plugin ID and option key."
+ - from: "capability.workspace-project-plugin-scope"
+ to: "boundary.workspace-plugin-trust"
+ type: "explicitly-bypasses"
+ rank: "must-inspect"
+ condition: "Workspace Plugin fields, Marketplace declarations and project roots enter discovery without Workspace trust or Plugin MCP approval; this exception must not widen to top-level project hooks, MCP or Subagent config."
+ - from: "boundary.workspace-plugin-trust"
+ to: "state.workspace-plugin-effective-config"
+ type: "does-not-filter"
+ rank: "must-inspect"
+ condition: "No trust or Plugin MCP approval state filters Workspace Plugin config; valid Plugin Hook/Skill/Command/Agent/MCP components all enter runtime, while manifest/schema/path validation remains fail-closed."
+ - from: "capability.workspace-project-plugin-scope"
+ to: "boundary.workspace-plugin-sensitive-config"
+ type: "allows"
+ rank: "must-inspect"
+ condition: "Workspace config may persist ordinary and sensitive Plugin options in plaintext; sensitive metadata masks UI, displays the Git risk warning and restricts template expansion but does not block persistence."
+ - from: boundary.workspace-plugin-sensitive-config
+ to: state.workspace-plugin-effective-config
+ type: merges-into
rank: must-inspect
- - from: capability.composer-trigger-routing
- to: surface.composer-skill-compatibility-picker
- type: renders-in
+ condition: Workspace option keys override User by Plugin ID and option key; sensitive values may reach MCP env/header/clientSecret and other valid Plugin runtime fields while logs, diagnostics, provider captures and replay artifacts stay redacted.
+ - from: "state.workspace-plugin-effective-config"
+ to: "persistence.plugin-marketplace-installed-cache"
+ type: "resolves-installed-package-from"
+ rank: "must-inspect"
+ condition: "Project enable is a declaration, not an install transaction; a missing target-Host package remains missing until the user explicitly installs it."
+ - from: "state.workspace-plugin-effective-config"
+ to: "state.plugin-reference-session-catalog"
+ type: "snapshots-into"
+ rank: "must-inspect"
+ condition: "New Session Apps and safely cold-resumed Apps freeze the latest effective Plugin capabilities and options; resident Apps do not hot-load config or package changes."
+ - from: "capability.workspace-project-plugin-scope"
+ to: "boundary.plugin-agent-host"
+ type: "executes-through"
+ rank: "must-inspect"
+ condition: "Local and remote project files are read from the target workspacePath on the authoritative Agent Host; one window Local Host routes remote workspaces through RemoteConnectionRegistry without a Remote Host process."
+ - from: "capability.workspace-project-plugin-scope"
+ to: "capability.workspace-identity"
+ type: "isolates-through"
+ rank: "must-inspect"
+ condition: "Filesystem reads use workspacePath; UI requests, async results, caches and Session association use workspaceIdentity?.trim() || workspacePath, with remoteSessionId preserved."
+ - from: "capability.workspace-project-plugin-scope"
+ to: "evidence.workspace-project-plugin-cases"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.plugin-reference"
+ to: "surface.composer-mention-plugins"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.composer-mention-plugins"
+ to: "surface.composer-at-context-picker"
+ type: "nested-in"
+ rank: "must-inspect"
+ condition: "Plugins are the first primary @ group while the Plugin chip and plugin:// carrier remain unchanged."
+ - from: "capability.plugin-reference"
+ to: "surface.plugin-store-example-prompt-try"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "For an installed Plugin, Try now starts a standard root draft with only the canonical plugin:// mention, while an example prompt appends the listing prompt; neither auto-sends, and disabled Plugin runtime eligibility remains unchanged."
+ - from: "surface.plugin-store-example-prompt-try"
+ to: "capability.conversation-runtime"
+ type: "commits-via"
+ rank: "must-inspect"
+ condition: "Both entries delegate to Root onCreateTask initialPrompt so workspace targeting, draft persistence, desktop workbench reset, and remote shared-host boundaries remain authoritative; an uninstalled Plugin shows Install instead of Try now, and example prompts only run the existing install action."
+ - from: "capability.plugin-reference"
+ to: "surface.new-task-suggested-prompts"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "A Plugin-backed recommendation adds the canonical plugin:// carrier only when target Host trusted resolution reports the fixed stable ID ready; otherwise it prefills the plain prompt. The suggested flow does not read the workspace reference catalog."
+ - from: "surface.new-task-suggested-prompts"
+ to: "capability.conversation-runtime"
+ type: "commits-via"
+ rank: "must-inspect"
+ condition: "Clicking replaces the workspace-scoped renderer draft through requestComposerTextInsert, persists and focuses the Composer, and never auto-sends."
+ - from: "surface.new-task-suggested-prompts"
+ to: "service.client-scenes"
+ type: "options-from"
+ rank: "must-inspect"
+ condition: "Only draft-suggestion.options.prompts.items are mapped; legacy ai-writing is not a fallback, labels and contents retain cn/en values, iconName uses the non-localized img field as a Lucide canonical name and ignores imgs, unknown names fall back to SquareCode, defaults reverse-reference option items for an optional Plugin, and missing or failed configuration renders an empty list without a static fallback."
+ - from: surface.new-task-suggested-prompts
+ to: capability.automations
+ type: opens-in
rank: must-inspect
- - from: surface.composer-at-context-picker
- to: service.composer-host-file-search
- type: options-from
- rank: conditional
- condition:
- Only file candidates use bounded Host file search; other mention providers keep their own
- option sources.
- - from: service.composer-host-file-search
- to: boundary.workspace-key
- type: isolated-by
+ condition: NAVIGATE:AUTOMATIONS opens the workspace Automations main view without prefilling; NAVIGATE:AUTOMATIONS:OFFPEAK also records automationTab=idle in renderer-local navigation history and selects Idle-time only after the visible tabs settle with idle present. The more specific action wins when both are declared.
+ - from: "surface.new-task-suggested-prompts"
+ to: "service.suggested-plugin-reference-resolution"
+ type: "validates-with"
+ rank: "must-inspect"
+ condition: "A recommendation without a Plugin writes immediately. A Plugin-backed recommendation performs one trusted resolution and one Composer replacement: initial ready writes the chip, optional display-only icon, and prompt together without opening a Popover; all other settled results write only the prompt. A renderer-local Composer revision guard drops the delayed write and follow-up feedback if the draft changes while resolving. Initial ready does not query a workspace reference catalog; install/enable success invalidates the deferred runtime and requires target Host trusted resolution to return ready before prepend. Missing Plugins refresh only ccbuddy-plugins-official; the Agent emits operation-scoped refreshing after the first local miss and before network refresh, which makes the single anchored Plugin action Popover enter loading. Disabled results enter loading before the plain-prompt insert. Refresh is aborted and fails closed at 10000ms without exposing old snapshot candidates. After Composer consumes the plain prompt, the same PopoverContent changes from loading to install or enable confirmation. Confirmation has no automatic timeout: a primary pointer-down outside the Popover closes it and cancels that operation, while focus changes, inside interaction, loading, mutation progress, success, and error do not use this confirmation dismissal rule. Every Popover root, PopoverContent, and button anchor remains the same mounted instance across loading, confirmation, mutation progress, retry, success, and failure; a second Popover is never mounted. The outer border-box remains 240px wide but has no fixed height in any phase. Install and enable success replace progress with the same vertical rolling structure as RollingToolbarLabel: AnimatePresence popLayout, outgoing content y -0.75em, incoming success y 0.75em to 0 with opacity, 200ms and ease [0.4, 0, 0.2, 1], while reduced motion replaces content immediately. Success uses the operation-specific localized message and closes exactly 2000ms after it appears. Install error diagnostics, rejects, and timeout write a UI warning log plus a localized warning toast, replace the old operationId, and restore the same PopoverContent to confirmation while keeping the plain prompt and dropping late results. Enable failure retains the same anchored error result behavior. Manual prompt takeover dismisses the old Plugin action Popover without a cancellation notice. Refresh/install/enable remain bound to one cancellable operationId at a time; API Key, OAuth, and user config are outside this flow."
+ - from: "service.suggested-plugin-reference-resolution"
+ to: "capability.plugin-source-acquisition"
+ type: "served-by"
+ rank: "must-inspect"
+ condition: "Only a newly refreshed canonical ccbuddy-plugins-official listing may become an install candidate; the previous successful snapshot is not sufficient after refresh failure."
+ - from: "service.suggested-plugin-reference-resolution"
+ to: "boundary.plugin-agent-host"
+ type: "executes-through"
+ rank: "must-inspect"
+ condition: "Resolution, refresh, User-scope installation, enablement, cancellation, and post-install catalog reads execute on the authoritative local or remote Agent Host selected by workspaceIdentity and remoteSessionId; trusted resolution carries both clientMode and deliveryKind without changing the existing desktop-continuous or web-remote-replayable profile."
+ - from: "service.suggested-plugin-reference-resolution"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ condition: "Desktop remains desktop-continuous; the operation does not create or send a Session."
+ - from: "surface.new-task-suggested-prompts"
+ to: "capability.workspace-identity"
+ type: "depends-on"
+ rank: "invariant-only"
+ condition: "The prefill request remains isolated by workspaceIdentity?.trim() || workspacePath and never writes a different local or remote draft."
+ - from: "capability.plugin-reference"
+ to: "state.plugin-reference-session-catalog"
+ type: "state-owned-by"
+ rank: "must-inspect"
+ - from: "capability.plugin-reference"
+ to: "service.plugin-reference-catalog"
+ type: "served-by"
+ rank: "must-inspect"
+ condition: "Picker state is request-identity scoped; workspace/session/remote attachment changes clear prior authority before loading and reject late results."
+ - from: "capability.plugin-reference"
+ to: "service.plugin-reference-reminder"
+ type: "served-by"
+ rank: "must-inspect"
+ condition: "Only persisted canonical displayInput is parsed; MCP live intersection excludes tools hidden by the current turn toolDisallowlist; Subagents require frozen declaration, a live profile, and Plugin-root provenance."
+ - from: "service.plugin-reference-reminder"
+ to: "persistence.plugin-reference-reminder-history"
+ type: "persists-to"
+ rank: "must-inspect"
+ condition: "Each generated reminder is appended as a model-only synthetic notice after its real user message."
+ - from: "persistence.plugin-reference-reminder-history"
+ to: "capability.conversation-runtime"
+ type: "hydrates-into"
+ rank: "must-inspect"
+ condition: "Cold resume and fork rebuild the original attachment text and causal order without re-resolving the current catalog or live inventory."
+ - from: "capability.plugin-reference"
+ to: "capability.conversation-runtime"
+ type: "depends-on"
+ rank: "should-inspect"
+ condition: "Reminder injection rides the turn-start pipeline; canonical message text stays unchanged."
+ - from: "capability.plugin-reference"
+ to: "capability.workspace-identity"
+ type: "depends-on"
+ rank: "should-inspect"
+ condition: "Catalog queries and caches key by workspaceIdentity?.trim() || workspacePath; remote picker reads the remote host store."
+ - from: "capability.plugin-reference"
+ to: "boundary.desktop-continuous"
+ type: "constrained-by"
+ rank: "invariant-only"
+ - from: "capability.plugin-reference"
+ to: "evidence.conversation-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.stateless-browser-use-mcp"
+ to: "service.shared-browser-use-mcp-runtime"
+ type: "executes-through"
+ rank: "must-inspect"
+ condition: "Only the built-in stateless node_repl opts into workspace sharing; ordinary dual-era third-party MCP stays session-isolated by default."
+ - from: "capability.stateless-browser-use-mcp"
+ to: "capability.browser-use-agent-guidance"
+ type: "constrains-kernel-lifecycle-guidance-in"
+ rank: "must-inspect"
+ condition: "Stateless migration may replace only persistent-kernel and cross-cell binding guidance; all unrelated Browser Use planning, safety, observation, and tab lifecycle rules remain aligned with the pre-upgrade contract."
+ - from: "capability.stateless-browser-use-mcp"
+ to: "state.browser-context-handle-registry"
+ type: "routes-browser-commands-through"
+ rank: "must-inspect"
+ condition: "MCP transport frontend and JavaScript kernel remain stateless; browser continuity remains in the existing session-scoped BrowserControl/tab owner and is rediscovered by stable tab facts on every call."
+ - from: "state.browser-context-handle-registry"
+ to: "boundary.workspace-key"
+ type: "isolated-by"
+ rank: "must-inspect"
+ condition: "Every BrowserControlPort call resolves sessionId through the authoritative registry, then restores workspaceIdentity?.trim() || workspacePath, remoteSessionId, runtimeScope, and clientMode; no model-visible handle or workspace path authorizes the call."
+ - from: "capability.stateless-browser-use-mcp"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.stateless-browser-use-mcp"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.stateless-browser-use-mcp"
+ to: "evidence.mcp-runtime-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.mcp-dual-era-version-negotiation"
+ to: "service.mcp-session-runtime"
+ type: "selects-wire-era-for"
+ rank: "must-inspect"
+ condition: "Unspecified stdio and Streamable HTTP servers default to auto; explicit legacy and deprecated SSE skip modern probing."
+ - from: "capability.stateless-browser-use-mcp"
+ to: "capability.mcp-dual-era-version-negotiation"
+ type: "pins-modern-through"
+ rank: "must-inspect"
+ condition: "The built-in node_repl pins 2026-07-28 and must never silently fall back to initialize or persistent legacy kernel semantics."
+ - from: "capability.mcp-dual-era-version-negotiation"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.mcp-dual-era-version-negotiation"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.mcp-dual-era-version-negotiation"
+ to: "evidence.mcp-runtime-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+
+ - from: "capability.mcp-oauth-authorization"
+ to: "surface.customize-capability-settings"
+ type: "renders-authorization-in"
+ rank: "must-inspect"
+ - from: capability.window-single-host-controller-r1
+ to: service.window-host-remote-connection-registry
+ type: owns
rank: must-inspect
- - from: capability.composer-trigger-routing
- to: capability.conversation-runtime
- type: preserves-canonical-input-for
+ condition: Each BrowserWindow owns one Host process; local, SSH, WSL, Docker, Server and Bot routes share its in-process registry.
+ - from: "capability.window-single-host-controller-r1"
+ to: "state.window-host-controller-projection"
+ type: "projects-through"
+ rank: "must-inspect"
+ condition: "The Host owns task membership, live overlay, source availability, offline retention, and list-originated write routing."
+ - from: "capability.window-single-host-controller-r1"
+ to: "boundary.window-host-scoped-service-attachment"
+ type: "exposes"
+ rank: "must-inspect"
+ condition: "One Host exposes a base port and validated remote-scoped ports without creating Remote Host processes."
+ - from: "state.window-host-controller-projection"
+ to: "capability.task-persistence"
+ type: "aggregates-without-owning"
+ rank: "must-inspect"
+ condition: "task-index remains the membership authority; sessions-index only overlays live facts and the projection is memory-only."
+ - from: "boundary.window-host-scoped-service-attachment"
+ to: "capability.remote-delivery"
+ type: "attaches-to"
+ rank: "must-inspect"
+ condition: "Main forwards authenticated attachment ports; ordinary Web/SSH carries typed RPC payloads without owning task, stream, queue, or snapshot state."
+ - from: "capability.window-single-host-controller-r1"
+ to: "capability.workspace-identity"
+ type: "isolates-through"
+ rank: "must-inspect"
+ condition: "All state keys and routes use workspaceIdentity with workspacePath fallback; remote scopes require identity and fail closed."
+ - from: boundary.window-host-scoped-service-attachment
+ to: boundary.desktop-continuous
+ type: injects-trusted-mode
rank: invariant-only
- condition:
- Changing candidate discovery or grouping must preserve the selected item representation
- submitted to the conversation.
- - from: surface.conversation-model-control
- to: state.composer-submission-intent
- type: edits-draft
- rank: must-inspect
- condition:
- The menu updates the next submission intent even for an existing conversation; it does
- not immediately switch the running model.
- - from: state.composer-submission-intent
- to: service.conversation-submission
- type: submits-selection-to
- rank: must-inspect
- condition: Sending captures modelSelection in the submission; subsequent menu edits affect later sends.
- - from: service.conversation-submission
- to: service.conversation-command-inbox
- type: requires-admission-through
- rank: must-inspect
- condition: V4 commands pass through CommandInbox before the gateway dispatches their handlers.
- - from: service.conversation-submission
- to: service.runtime-submission-execution
- type: schedules-selection-for
- rank: must-inspect
- condition:
- CLI applies admitted configuration when execution starts, or when a Guide reaches the next
- model step.
- - from: service.runtime-submission-execution
- to: state.conversation-product-projection
- type: emits-model-selection-for
+ condition: Desktop ports stay direct continuous and never merge Web/SSH replayable recovery frames.
+ - from: "capability.window-single-host-controller-r1"
+ to: "evidence.ssh-remote-p0-e2e"
+ type: "covered-by"
+ rank: "evidence-only"
+ condition: "Formal real Ubuntu SSH flows cover failure/retry and two-workspace task isolation while keeping one Host PID; pending SSH-P0-CONN-03 plans same-target pending cancel with new-credential retry; renderer reload remains covered by the independent R1-RELOAD-01 case."
+
+ - from: "capability.unified-ccbuddy-lite-server"
+ to: "surface.multi-server-controller-ui"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: surface.multi-server-controller-ui
+ to: state.controller-server-directory
+ type: projects-from
rank: must-inspect
- condition: Applied runtime model selection is projected through ModelSelected events.
+ condition: Desktop and Web keep independent controller-local connection lists.
+ - from: "state.controller-server-directory"
+ to: "service.multi-server-controller"
+ type: "connects-through"
+ rank: "must-inspect"
+ - from: "service.multi-server-controller"
+ to: "service.ccbuddy-stable-launcher"
+ type: "bootstraps-through"
+ rank: "must-inspect"
+ - from: "service.ccbuddy-stable-launcher"
+ to: "persistence.ccbuddy-server-identity"
+ type: "owns"
+ rank: "must-inspect"
+ condition: "serverId is persistent per OS user/data root and independent from SSH account, address, and connection route."
+ - from: "service.multi-server-controller"
+ to: "boundary.unified-server-transport"
+ type: "routes-through"
+ rank: "must-inspect"
+ - from: "boundary.unified-server-transport"
+ to: "capability.conversation-runtime"
+ type: "carries-without-changing"
+ rank: "invariant-only"
+ condition: "MessagePort, WebSocket, and SSH/WSL/Docker tunnels carry the same typed service and V4 conversation contracts."
+ - from: "capability.unified-ccbuddy-lite-server"
+ to: "capability.workspace-identity"
+ type: "rekeys-with-server-identity"
+ rank: "must-inspect"
+ condition: "Global references use serverId plus server-local workspaceIdentity; workspacePath remains execution/display only."
+ - from: "capability.unified-ccbuddy-lite-server"
+ to: "capability.prompt-attachment-materialization"
+ type: "replaces-host-path-with-resource-store-for"
+ rank: "must-inspect"
+ - from: "capability.prompt-attachment-materialization"
+ to: "persistence.ccbuddy-resource-store"
+ type: "persists-source-server-resource-to"
+ rank: "must-inspect"
+ condition: "Long paste, image, and file bytes commit to the consuming Source Server before prompt admission."
+ - from: "capability.unified-ccbuddy-lite-server"
+ to: "capability.provider-registry"
+ type: "keeps-server-local-and-syncs-explicitly"
+ rank: "must-inspect"
+ condition: "Provider definitions can be directionally mirrored; OAuth, runtime-only headers, display order, and UI selection state remain local."
+ - from: "capability.unified-ccbuddy-lite-server"
+ to: "capability.remote-delivery"
+ type: "uses-existing-replayable-transport-for"
+ rank: "must-inspect"
+ condition: "Ordinary Web/SSH remains web-remote-replayable and uses the existing server or workspace runtime."
+ - from: "capability.unified-ccbuddy-lite-server"
+ to: "boundary.desktop-continuous"
+ type: "must-not-change-delivery"
+ rank: "invariant-only"
+ - from: "capability.unified-ccbuddy-lite-server"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery"
+ rank: "invariant-only"
+
+ - from: "capability.workspace-hook-persistent-trust"
+ to: "surface.workspace-hook-settings-trust-row"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.workspace-hook-settings-trust-row"
+ to: "state.workspace-hook-review-renderer-binding"
+ type: "requests-and-waits-through"
+ rank: "must-inspect"
+ condition: "Static Trust state controls visibility only; mutation waits for the same workspace identity, bundle digest, and review item in an immutable Runtime request."
+ - from: "state.workspace-hook-review-renderer-binding"
+ to: "service.workspace-hook-runtime-review-controller"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "surface.workspace-hook-settings-trust-row"
+ to: "service.workspace-hook-settings-pretrust"
+ type: "falls-back-to-without-session"
+ rank: "must-inspect"
+ condition: "When no task/session command binding exists, the Host starts only a read-only Agent control plane and never creates a hidden task."
+ - from: "service.workspace-hook-settings-pretrust"
+ to: "state.workspace-hook-managed-policy"
+ type: "must-authorize-mutation-with"
+ rank: "must-inspect"
+ condition: "The no-session Settings RPC reads the same trusted Host-owned provider as session Runtime; deny and allow_trusted_only reject before Trust store mutation."
+ - from: "service.workspace-hook-runtime-review-controller"
+ to: "state.workspace-hook-managed-policy"
+ type: "must-authorize-mutation-with"
+ rank: "must-inspect"
+ condition: "Session review and no-session pretrust share one provider instance so policy cannot diverge by UI path."
+ - from: "service.workspace-hook-settings-pretrust"
+ to: "persistence.workspace-hook-trust-store"
+ type: "revalidates-and-persists-exact-declaration-to"
+ rank: "must-inspect"
+ condition: "The Agent rediscovers the canonical snapshot and matches workspace identity, bundle digest, and declaration digest before mutation."
+ - from: "service.workspace-hook-runtime-review-controller"
+ to: "persistence.workspace-hook-trust-store"
+ type: "persists-exact-declaration-trust-to"
+ rank: "must-inspect"
+ - from: "capability.workspace-hook-persistent-trust"
+ to: "capability.workspace-identity"
+ type: "isolates-by"
+ rank: "must-inspect"
+ condition: "Authorization uses workspaceIdentity.trim() or workspacePath fallback; workspacePath alone never aliases distinct remote workspaces."
+ - from: "capability.workspace-hook-persistent-trust"
+ to: "boundary.desktop-continuous"
+ type: "projects-review-without-changing"
+ rank: "invariant-only"
+ - from: "capability.workspace-hook-persistent-trust"
+ to: "boundary.web-remote-replayable"
+ type: "recovers-review-without-bypassing"
+ rank: "invariant-only"
+ - from: "capability.workspace-hook-persistent-trust"
+ to: "evidence.workspace-hook-persistent-trust-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.conversation-hook-turn-summary"
+ to: "surface.conversation-turn-hook-summary"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "surface.conversation-turn-hook-summary"
+ to: "state.conversation-projection"
+ type: "reads-client-safe-turn-summary-from"
+ rank: "must-inspect"
+ condition: "Renderer groups authoritative hookInvocation rows by turnId and never infers placement from the nearest assistant row."
+ - from: "capability.conversation-hook-turn-summary"
+ to: "capability.hooks-settings"
+ type: "shares-source-vocabulary-with"
+ rank: "should-inspect"
+ condition: "User, Project, and Plugin remain distinct sources; the conversation surface does not edit settings or Trust."
+ - from: "capability.conversation-hook-turn-summary"
+ to: "boundary.desktop-continuous"
+ type: "projects-safe-summary-through"
+ rank: "must-inspect"
+ condition: "Desktop receives direct continuous hook summary updates without adopting replayable recovery semantics."
+ - from: "capability.conversation-hook-turn-summary"
+ to: "boundary.web-remote-replayable"
+ type: "recovers-safe-summary-through"
+ rank: "must-inspect"
+ condition: "Ordinary Web/SSH receives only client-safe summary fields through the existing replayable snapshot/gap boundary."
+ - from: "capability.conversation-hook-turn-summary"
+ to: "evidence.conversation-catalog"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.subagent-computer-use-boundary"
+ to: "capability.agent-core-runtime"
+ type: "guards-child-runtime"
+ rank: "must-inspect"
+ - from: "capability.subagent-computer-use-boundary"
+ to: "capability.mcp-dual-era-version-negotiation"
+ type: "filters-borrowed-mcp-surface"
+ rank: "must-inspect"
+ - from: "capability.subagent-computer-use-boundary"
+ to: "capability.plugin-reference"
+ type: "filters-official-skill-provenance"
+ rank: "should-inspect"
+ - from: "capability.subagent-computer-use-boundary"
+ to: "evidence.mcp-runtime-tests"
+ type: "covered-by"
+ rank: "evidence-only"
+ - from: "capability.workflow-run-confirmation"
+ to: "surface.workflow-run-confirmation-dialog"
+ type: "renders-ask-preview-in"
+ rank: "must-inspect"
+ condition: "Ask fires only when the analysis is clean; typecheck failures return diagnostics with no dialog. Options are Run/Deny only (optionsPolicy no-always-allow across all three option-build sites)."
+ - from: "surface.workflow-run-confirmation-dialog"
+ to: "surface.parent-task-blocking-interaction"
+ type: "shares-dialog-host-with"
+ rank: "must-inspect"
+ condition: "Same PermissionDialog host and resolveInteraction commit path; the workflow branch replaces the generic block and skips displayReason. The script fold is a recorded exception to the no-collapse permission-block rule: the graph stays force-open as the decision-critical content."
+ - from: "capability.workflow-run-confirmation"
+ to: "surface.conversation-tool-call"
+ type: "must-match-tool-identity-split-of"
+ rank: "invariant-only"
+ condition: "Both surfaces dispatch on resolveToolCallIdentity family workflow so pre- and post-approval semantics never drift."
+
+ - from: "capability.workflow-live-run"
+ to: "capability.workflow-run-confirmation"
+ type: "starts-only-after-allow-from"
+ rank: "must-inspect"
+ condition: "The engine starts on the confirmation gate's Allow in any mode. The broken-script path is unchanged: prepareApproval rules proceed, the handler returns diagnostics, and no dwf_run row is created. CreateWorkflowOutputSchema is .strict(), so status/backgroundTaskId is an explicit schema change, and metadata.readOnly flips true to false."
+ - from: "capability.workflow-live-run"
+ to: "state.workflow-run-journal"
+ type: "delegates-run-truth-to"
+ rank: "must-inspect"
+ condition: "The engine owns createRun; the submit path must never pre-insert a dwf_run row, since a pre-existing row flips the constructor into its resume branch and a new run would silently resume from an empty journal. createRun grows scriptText/scriptHash/parentSessionId/cwd, and listEvents grows an afterSequence/limit cursor whose semantics must be proven by the shared journal contract suite (src/testing/journal-contract.ts) against both the in-memory and SQLite implementations."
+ - from: "capability.workflow-live-run"
+ to: "state.conversation-projection"
+ type: "adds-authoritative-state-key-to"
+ rank: "must-inspect"
+ condition: "Run events append bounded session events to the PARENT session, through the projection reducer, reduced into the workflowRuns state.updated key (bounded runs/actors/nodes arrays, lastEventSequence). Precedent is the subagents key. Recorded deviation from execution-engine.md: state.updated is not profile-gated, justified by transition-frequency rather than token-frequency events; run-settled emits the terminal authoritative patch."
+ - from: "capability.workflow-live-run"
+ to: "capability.subagents"
+ type: "reuses-background-lifecycle-pattern-of"
+ rank: "must-inspect"
+ condition: "The five hardcoded tool-name dispatch sites in background-tasks.ts become a per-tool port lookup rather than a fifth special case. canCancelBackgroundTask is one of them and is load-bearing: it hard-returns false for Workflow today, so cancellable is false and the Cancel button is dead without it. A new narrow DynamicWorkflowRunPort is added; the legacy WorkflowPort is NOT widened. Cancellation adds no RPC: the details page and the backgroundWorks panel share the existing cancelBackgroundWork command with workId = runId, and the run service's AbortController registry is that port's cancel(runId) behind it."
+ - from: "capability.workflow-live-run"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-change-recovery"
+ rank: "invariant-only"
+ condition: "workflowRuns is authoritative on every patch, so replayable clients resume complete with no terminal-upsert machinery. No version-skew defense is needed on the v4 wire — verified: statePatchSchema is a non-strict z.object under zod ^4.3.6, so an old desktop strips the unknown key and keeps the rest of the patch. Do NOT re-litigate this against the CreateWorkflow-display precedent, which had the opposite shape (.strict() schemas made an old desktop discard the whole permission.requested event). The new session event type is stripped at shouldExposeSessionEventToProtocol, the same seam as the existing StreamingToolLedgerUpdated strip, for bandwidth rather than to prevent event loss."
+ - from: "capability.workflow-live-run"
+ to: "surface.workflow-run-detail-pane"
+ type: "renders-live-progress-in"
+ rank: "must-inspect"
+ condition: "Live view v1 is the STATIC causality graph plus a status overlay, not the unrolled instance graph. Status joins by site id and aggregates over instances (any running wins, then failed, then all-settled done, else pending); engine phases dispatched/repairing/nudged map to running, settled ok including cached to done, failed/cancelled to failed. The engine emits no executing event."
+ - from: "surface.workflow-run-detail-pane"
+ to: "surface.workspace-side-pane-tab-strip"
+ type: "adds-tab-type-to"
+ rank: "must-inspect"
+ condition: "New workflow-run tab type in the WorkspaceSidePaneTab union, shaped after PlanDetailSidePaneTab. Opened from the CreateWorkflow tool card following the ExitPlanMode onOpenPlanDetail precedent: the host injects parent/session scope, the card only emits intent."
+ - from: "surface.workflow-run-detail-pane"
+ to: "surface.workflow-run-confirmation-dialog"
+ type: "shares-causality-graph-component-with"
+ rank: "must-inspect"
+ condition: "Both render WorkflowCausalityGraph. The statuses and animatedEdges props plus step-node status styling and both locales' status labels already exist from phase 1; the live feature only supplies data, adding no visual vocabulary."
+ - from: "surface.workflow-run-detail-pane"
+ to: "surface.workflow-actor-transcript"
+ type: "drills-down-into"
+ rank: "must-inspect"
+ condition: "Clicking an ask step resolves actor instances from workflowRuns.actors: a single instance opens the transcript tab, multiple instances (fan-out lane families) open a per-instance status picker. World-read steps are selection-only, since the workspace lane has no session."
+ - from: "surface.workflow-actor-transcript"
+ to: "surface.subagent-session-detail"
+ type: "reuses-read-only-session-composition-of"
+ rank: "must-inspect"
+ condition: "Nested read-only SessionPane under V4PaneConversationProvider, composed exactly as SubagentSessionSidePane does. This is only reachable because actor sessions become really persisted: the production ActorRuntimeFactory gains ensureSessionPersistedForExternalActivity, subscribeEvents and createSessionTaskLink, and session ids become run-scoped and charset-safe (today's sess_wf-actor-actor#1@1 collides across concurrent runs and carries #/@)."
+ - from: "capability.workflow-live-run"
+ to: "surface.conversation-tool-call"
+ type: "keeps-bounded-display-on"
+ rank: "invariant-only"
+ condition: "The tool card keeps the bounded display channel and gains only the run entry point; the full view lives in the side pane and reads the workflowRuns projection instead of re-deriving from display."
+
+ - from: "capability.workflow-run-introspection"
+ to: "state.workflow-run-journal"
+ type: "reads-run-truth-from"
+ rank: "must-inspect"
+ condition: "Strictly read-only: listRuns/getRunDetail live on the SQLite adapter and are capability-probed, NOT added to the domain JournalStorePort (listNonTerminalRuns precedent — the engine never enumerates runs, and the in-memory journal stays untouched). The tools never write the journal: possiblyInterrupted is a read-side annotation; orphan reconciliation authority stays with the owning session's app construction. Migration 0021 adds dwf_run.name plus the (cwd, time_updated) index; name rides submit → EngineConfig → createRun, keeping createRun engine-exclusive."
+ - from: "capability.workflow-run-introspection"
+ to: "capability.workflow-live-run"
+ type: "extends-read-surface-of"
+ rank: "must-inspect"
+ condition: "runId ≡ taskId ≡ workId. The tools complement, not replace, the existing surfaces: completion notifications stay the default for this session's runs (tool descriptions must steer models away from polling), TaskOutput stays the blocking-wait tool, and GetWorkflowRun serves cross-session runs the runtime task registry cannot. Both tools declare readOnly/needsApproval:false so they auto-allow in plan and build mode; they must never inherit CreateWorkflow's alwaysAsk gate."
+ - from: "surface.submit-result-tool-card"
+ to: "surface.workflow-actor-transcript"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: 'The actor transcript is a nested read-only SessionPane, so submit_result renders through the SAME ToolCallBlocks registry as the main conversation — one renderer fixes both surfaces; there is nothing pane-specific to special-case. Routing requires the literal snake_case name "submit_result" in CCBUDDY_KNOWN_TOOL_NAMES (family workflow) plus a toolName dispatch inside resolveRenderer''s workflow case (RespondToCoordinator precedent). Pure read-time projection: the payload lives entirely in input.result (output is only {status:"accepted"}), real models often submit it as a JSON-string (engine scheduler leniency mirrored read-side, one lenient JSON.parse max), and no display-union member is added — that union requires four-file lockstep and a missing member degrades silently.'
+
+ - from: "capability.official-server-mcp"
+ to: "capability.plan-entitlements"
+ type: "authenticates-and-reads-quota-with"
+ rank: "must-inspect"
+ condition: "Product planScope and transport wireScope are separate; ZAI Team is TEAM for quota but keeps Off-Peak-compatible fallback headers. Entitlement gates tools/call only, never connection or tool advertisement."
+ - from: "capability.mcp-settings-diagnostics"
+ to: "capability.official-server-mcp"
+ type: "projects-connection-diagnostics-from"
+ rank: "must-inspect"
+ condition: "Only connection, initialize and tools/list diagnostics reach Settings; tools/call entitlement, quota and request ids remain on their tool result surface."
+ - from: "capability.mcp-settings-diagnostics"
+ to: "surface.customize-capability-settings"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "Direct MCP uses Installed and plugin MCP stays grouped by stable pluginId; Commands, Skills and MCP share structural list primitives without sharing resource state or mutations."
+ - from: "capability.mcp-settings-diagnostics"
+ to: "capability.workspace-identity"
+ type: "isolated-by"
+ rank: "must-inspect"
+ condition: "Status snapshots, failure diagnostics and Plugin projections use workspaceIdentity.trim() or workspacePath fallback and reject stale refresh epochs."
+ - from: "capability.official-server-mcp"
+ to: "capability.mcp-dual-era-version-negotiation"
+ type: "injects-current-identity-during-negotiation-for"
+ rank: "must-inspect"
+ condition: "Trusted official HTTP server/discover, initialize, initialized notifications, ping, tools/list and GET probes resolve and send current identity headers; trusted official stdio messages carry the same identity payload in namespaced _meta. Resolution failure degrades to an explicit anonymous or failure payload, while untrusted origins still fail before credential access or network access."
+ - from: "capability.official-server-mcp"
+ to: "capability.mcp-async-tool-catalog"
+ type: "separates-advertisement-from-execution-for"
+ rank: "must-inspect"
+ condition: "Connected status and advertised or cached tool schemas carry no entitlement authority; every official transport request re-resolves credentials, and the server authorizes tools/call before side effects or billing."
+ - from: "capability.official-server-mcp"
+ to: "capability.workspace-identity"
+ type: "audits-by"
+ rank: "must-inspect"
+ condition: "Credential issuance audit uses workspaceIdentity.trim() or workspacePath fallback; workspacePath remains execution/display only."
+ - from: "capability.official-server-mcp"
+ to: "boundary.desktop-continuous"
+ type: "projects-banner-without-changing"
+ rank: "invariant-only"
+ - from: "capability.conversation-sharing"
+ to: "surface.conversation-share-publish"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "Only Desktop local, SSH, WSL, and Docker surfaces publish; ordinary Web remains unavailable."
+ - from: "surface.conversation-share-publish"
+ to: "service.conversation-share"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "capability.conversation-sharing"
+ to: "surface.conversation-share-handover"
+ type: "renders-in"
+ rank: "must-inspect"
+ condition: "Only desktop-continuous imported sessions expose the handover chip/browser path; web-remote-replayable never creates it."
+ - from: "surface.conversation-share-handover"
+ to: "shared-ui.conversation-share-context-chip"
+ type: "shares-component"
+ rank: "must-inspect"
+ - from: "surface.conversation-share-handover"
+ to: "persistence.conversation-share-context"
+ type: "commits-to"
+ rank: "must-inspect"
+ - from: "persistence.conversation-share-context"
+ to: "capability.agent-core-runtime"
+ type: "projects-provider-context-with"
+ rank: "must-inspect"
+ condition: "pending/reserved/discarded are excluded from provider history; attached is injected once and survives cold hydration."
+ - from: "service.conversation-share"
+ to: "service.ccbuddy-agent-connection-scope"
+ type: "reads-rows-and-file-changes-through"
+ rank: "must-inspect"
+ condition: "Desktop local and SSH/WSL/Docker publication reuse the current MessagePort attachment scope; Share never calls raw Agent V4 queries or creates a second connection scope."
+ - from: "service.ccbuddy-agent-connection-scope"
+ to: "boundary.desktop-continuous"
+ type: "injects-trusted-client-mode"
+ rank: "must-inspect"
+ - from: "service.ccbuddy-agent-connection-scope"
+ to: "boundary.web-remote-replayable"
+ type: "must-not-authorize-share-through"
+ rank: "must-inspect"
+ - from: "capability.conversation-sharing"
+ to: "capability.workspace-identity"
+ type: "isolated-by"
+ rank: "must-inspect"
+ condition: "Remote publication carries workspaceIdentity and remoteSessionId while workspacePath remains execution-only."
+ - from: "capability.conversation-sharing"
+ to: "boundary.desktop-continuous"
+ type: "allowed-through"
+ rank: "must-inspect"
+ - from: "capability.conversation-sharing"
+ to: "boundary.web-remote-replayable"
+ type: "must-remain-isolated-from"
+ rank: "must-inspect"
+ - from: "capability.official-server-mcp"
+ to: "boundary.web-remote-replayable"
+ type: "projects-banner-without-changing"
+ rank: "invariant-only"
+
+ - from: "capability.background-bash-output"
+ to: "service.ccbuddy-agent-connection-scope"
+ type: "queries-through"
+ rank: "must-inspect"
+ - from: "capability.background-bash-output"
+ to: "boundary.desktop-continuous"
+ type: "preserves"
+ rank: "invariant-only"
+ - from: "capability.background-bash-output"
+ to: "boundary.web-remote-replayable"
+ type: "preserves"
+ rank: "invariant-only"
+
+ - from: "surface.composer-at-context-picker"
+ to: "service.composer-host-file-search"
+ type: "options-from"
+ condition: "Only file mentions use bounded Host search; other file pickers retain the packed listing API and canonical mention insertion remains unchanged."
+
+ - from: "capability.bot-group-collaboration"
+ to: "surface.bot-bound-groups"
+ type: "renders-in"
+ rank: "must-inspect"
+ - from: "capability.bot-group-collaboration"
+ to: "surface.bot-channel-messages"
+ type: "projects-to"
+ rank: "must-inspect"
+ - from: "capability.bot-group-collaboration"
+ to: "service.ccbuddy-agent-connection-scope"
+ type: "commits-through"
+ rank: "must-inspect"
+ condition: "CLI owns admission and execution; Bot Host owns conversation binding and durable delivery."
+ - from: "capability.bot-group-collaboration"
+ to: "boundary.web-remote-replayable"
+ type: "must-remain-isolated-from"
+ rank: "invariant-only"
+ condition: "Bot output stays bot-channel-continuous; ordinary Web/SSH input and recovery retain replayable semantics."
diff --git a/.agents/skills/feature-boundary-planner/references/impact-brief-template.md b/.agents/skills/feature-boundary-planner/references/impact-brief-template.md
index c7a3deb1..1e8132d2 100644
--- a/.agents/skills/feature-boundary-planner/references/impact-brief-template.md
+++ b/.agents/skills/feature-boundary-planner/references/impact-brief-template.md
@@ -1,6 +1,6 @@
# Impact Brief Template
-Use this template for every feature-impact scan. Keep it compact enough to search and compare. Complete sections relevant to the request and explicitly identify unavailable evidence.
+Use this template for every feature-impact scan. Keep it compact enough to search and compare. Replace empty cells; do not delete a section merely because the result is “none”.
## Feature Summary
@@ -48,17 +48,23 @@ Use this template for every feature-impact scan. Keep it compact enough to searc
| --------- | -------------- | ------------ | -------- |
| | | | |
-## Source Evidence
+## Codegraph Evidence
-| File / symbol | Inspection method | Direct callers / key path | Interpretation |
-| ------------- | ------------------------------------------------- | ------------------------- | -------------- |
-| | source / dep:refs / available codegraph / runtime | | |
+| Seed | Query | Direct callers / key path | Depth | Interpretation |
+| ---- | ----------------------------------------------------- | ------------------------- | ----- | -------------- |
+| | explore / callers / callees / impact / affected tests | | 1-3 | |
-## Evidence Gaps
+## Graph Drift Candidates
-| Unresolved behavior | Available evidence | Missing evidence | Next verification |
-| ------------------- | ------------------ | ---------------- | ----------------- |
-| none or item | | | |
+| Candidate | Live-code evidence | Missing/stale graph relation | Proposed follow-up |
+| ------------ | ------------------ | ---------------------------- | ------------------------- |
+| none or item | | | confirm / update / ignore |
+
+## Graph Delta
+
+| Status | Node/edge | Semantic reason | Evidence | Action |
+| --------------------------- | --------- | --------------- | ---------------------- | -------------------------------------------- |
+| none / proposed / confirmed | | | code/doc/user decision | impact-only: report / planning: update graph |
## Unresolved Questions
diff --git a/.agents/skills/react-best-practices/AGENTS.md b/.agents/skills/react-best-practices/AGENTS.md
index d0f25735..a24bdd5e 100644
--- a/.agents/skills/react-best-practices/AGENTS.md
+++ b/.agents/skills/react-best-practices/AGENTS.md
@@ -113,7 +113,7 @@ Waterfalls are the #1 performance killer. Each sequential await adds full networ
When a branch uses `await` for a flag or remote value and also requires a **cheap synchronous** condition (local props, request metadata, already-loaded state), evaluate the cheap condition **first**. Otherwise you pay for the async call even when the compound condition can never be true.
-This is a specialization of [Defer Await Until Needed](rules/async-defer-await.md) for `flag && cheapCondition` style checks.
+This is a specialization of [Defer Await Until Needed](./async-defer-await.md) for `flag && cheapCondition` style checks.
**Incorrect:**
@@ -216,7 +216,7 @@ async function updateResource(resourceId: string, userId: string) {
This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.
-For `await getFlag()` combined with a cheap synchronous guard (`flag && someCondition`), see [Check Cheap Conditions Before Async Flags](rules/async-cheap-condition-before-await.md).
+For `await getFlag()` combined with a cheap synchronous guard (`flag && someCondition`), see [Check Cheap Conditions Before Async Flags](./async-cheap-condition-before-await.md).
### 1.3 Dependency-Based Parallelization
@@ -877,7 +877,7 @@ Safe exceptions:
- Process-wide singletons that do not store request- or user-specific mutable data
-For static assets and config, see [Hoist Static I/O to Module Level](rules/server-hoist-static-io.md).
+For static assets and config, see [Hoist Static I/O to Module Level](./server-hoist-static-io.md).
### 3.4 Cross-Request LRU Caching
diff --git a/.agents/skills/react-best-practices/README.md b/.agents/skills/react-best-practices/README.md
index 57412d7b..e48a52cd 100644
--- a/.agents/skills/react-best-practices/README.md
+++ b/.agents/skills/react-best-practices/README.md
@@ -1,14 +1,127 @@
# React Best Practices
-This directory contains the vendored React and Next.js guidance used by the skill.
+A structured repository for creating and maintaining React Best Practices optimized for agents and LLMs.
-- [SKILL.md](SKILL.md): scope and rule selection.
-- [AGENTS.md](AGENTS.md): combined reference.
-- [rules/](rules/): individual rules and examples.
-- [metadata.json](metadata.json): source metadata.
+## Structure
-When changing a rule, update its individual file and the corresponding section in the combined reference. Use [the rule template](rules/_template.md) and [section metadata](rules/_sections.md) for structure. Keep relative links valid from the file that contains them.
+- `rules/` - Individual rule files (one per rule)
+ - `_sections.md` - Section metadata (titles, impacts, descriptions)
+ - `_template.md` - Template for creating new rules
+ - `area-description.md` - Individual rule files
+- `src/` - Build scripts and utilities
+- `metadata.json` - Document metadata (version, organization, abstract)
+- **`AGENTS.md`** - Compiled output (generated)
+- **`test-cases.json`** - Test cases for LLM evaluation (generated)
-The examples illustrate framework patterns; apply only rules relevant to the target application's framework and execution environment.
+## Getting Started
+
+1. Install dependencies:
+
+ ```bash
+ pnpm install
+ ```
+
+2. Build AGENTS.md from rules:
+
+ ```bash
+ pnpm build
+ ```
+
+3. Validate rule files:
+
+ ```bash
+ pnpm validate
+ ```
+
+4. Extract test cases:
+ ```bash
+ pnpm extract-tests
+ ```
+
+## Creating a New Rule
+
+1. Copy `rules/_template.md` to `rules/area-description.md`
+2. Choose the appropriate area prefix:
+ - `async-` for Eliminating Waterfalls (Section 1)
+ - `bundle-` for Bundle Size Optimization (Section 2)
+ - `server-` for Server-Side Performance (Section 3)
+ - `client-` for Client-Side Data Fetching (Section 4)
+ - `rerender-` for Re-render Optimization (Section 5)
+ - `rendering-` for Rendering Performance (Section 6)
+ - `js-` for JavaScript Performance (Section 7)
+ - `advanced-` for Advanced Patterns (Section 8)
+3. Fill in the frontmatter and content
+4. Ensure you have clear examples with explanations
+5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json
+
+## Rule File Structure
+
+Each rule file should follow this structure:
+
+````markdown
+---
+title: Rule Title Here
+impact: MEDIUM
+impactDescription: Optional description
+tags: tag1, tag2, tag3
+---
+
+## Rule Title Here
+
+Brief explanation of the rule and why it matters.
+
+**Incorrect (description of what's wrong):**
+
+```typescript
+// Bad code example
+```
+````
+
+**Correct (description of what's right):**
+
+```typescript
+// Good code example
+```
+
+Optional explanatory text after examples.
+
+Reference: [Link](https://example.com)
+
+## File Naming Convention
+
+- Files starting with `_` are special (excluded from build)
+- Rule files: `area-description.md` (e.g., `async-parallel.md`)
+- Section is automatically inferred from filename prefix
+- Rules are sorted alphabetically by title within each section
+- IDs (e.g., 1.1, 1.2) are auto-generated during build
+
+## Impact Levels
+
+- `CRITICAL` - Highest priority, major performance gains
+- `HIGH` - Significant performance improvements
+- `MEDIUM-HIGH` - Moderate-high gains
+- `MEDIUM` - Moderate performance improvements
+- `LOW-MEDIUM` - Low-medium gains
+- `LOW` - Incremental improvements
+
+## Scripts
+
+- `pnpm build` - Compile rules into AGENTS.md
+- `pnpm validate` - Validate all rule files
+- `pnpm extract-tests` - Extract test cases for LLM evaluation
+- `pnpm dev` - Build and validate
+
+## Contributing
+
+When adding or modifying rules:
+
+1. Use the correct filename prefix for your section
+2. Follow the `_template.md` structure
+3. Include clear bad/good examples with explanations
+4. Add appropriate tags
+5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json
+6. Rules are automatically sorted by title - no need to manage numbers!
+
+## Acknowledgments
Originally created by [@shuding](https://x.com/shuding) at [Vercel](https://vercel.com).
diff --git a/.dockerignore b/.dockerignore
index 5c946dd6..e3df600c 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -3,6 +3,8 @@
.husky
.worktree
.vscode
+.codex
+.claude
.agents
node_modules
@@ -19,6 +21,7 @@ node_modules
dogfood-output
docs/electron
docs/webdriverio
+packages/desktop/.e2e-home
packages/desktop/bundled-agents
packages/desktop/bundled-resources
packages/desktop/bundled-tools
diff --git a/.env.development b/.env.development
index 58969bb9..35536ff7 100644
--- a/.env.development
+++ b/.env.development
@@ -1 +1,5 @@
-# 默认使用线上接口;本地地址覆盖写入未跟踪的 .env 或 .env.local。
+# 本地开发默认使用测试产品环境;当前环境由启动脚本注入 CCBUDDY_ENV。
+CCBUDDY_TEST_BASE_URL=https://ccbuddy.z.ai
+ZAI_TEST_OAUTH_ORIGIN=https://chat.z.ai
+ZAI_TEST_BUSINESS_BASE_URL=https://api.z.ai
+ZAI_TEST_OAUTH_CLIENT_ID=client_P8X5CMWmlaRO9gyO-KSqtg
diff --git a/.env.production b/.env.production
index e509877c..6bd86c3f 100644
--- a/.env.production
+++ b/.env.production
@@ -1 +1,6 @@
-# 默认线上地址由 shared endpoint resolver 提供;覆盖请使用通用变量。
+# 正式环境链接常量;当前环境由构建脚本或 CI 注入 CCBUDDY_ENV。
+# client_id 是 OAuth 公开标识,不是 secret。
+CCBUDDY_PRODUCTION_BASE_URL=https://ccbuddy.z.ai
+ZAI_PRODUCTION_OAUTH_ORIGIN=https://chat.z.ai
+ZAI_PRODUCTION_BUSINESS_BASE_URL=https://api.z.ai
+ZAI_PRODUCTION_OAUTH_CLIENT_ID=client_P8X5CMWmlaRO9gyO-KSqtg
diff --git a/.gitattributes b/.gitattributes
index bf31b658..9448cdd7 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -2,10 +2,15 @@
*.mjs text eol=lf
*.sh text eol=lf
+# E2E PDF fixture 含规范 xref 尾空格,必须按二进制处理,避免 diff --check 误报。
+packages/desktop/test/e2e/fixtures/fs/**/*.pdf binary
+
# 上游许可原文与生成的声明按字节保存,不能自动转换换行或裁剪空白。
# 原文中的 License / ======= 是 Markdown 标题,不是合并冲突。
THIRD-PARTY-NOTICES.md -text -whitespace conflict-marker-size=32 linguist-generated=true
third-party/upstream/** -text -whitespace
third-party/native-search/licenses/** -text -whitespace
third-party/runtime/*.txt -text -whitespace
+docs/knip/docs/LICENSE -text -whitespace
+docs/knip/docs/licenses/** -text -whitespace
third-party/inventory.json linguist-generated=true
diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml
index 34e5a8b7..8f7850c6 100644
--- a/.github/workflows/pr-build.yml
+++ b/.github/workflows/pr-build.yml
@@ -35,9 +35,10 @@ jobs:
pnpm lint
pnpm fmt:check
pnpm architecture:check --changed
- - name: Test desktop update, release manifest, and main release allocation policy
+ - name: Test desktop packaging, update, release manifest, and main release allocation policy
run: >-
pnpm exec tsx --test
+ packages/desktop/test/packaged-cua-koffi.test.mjs
packages/desktop/test/ccbuddy-update-policy.test.mjs
packages/desktop/test/finalize-macos-release.test.mjs
packages/desktop/test/legacy-native-update.test.mjs
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
index 895bb11a..550f31c3 100644
--- a/.oxfmtrc.json
+++ b/.oxfmtrc.json
@@ -7,6 +7,8 @@
"third-party/inventory.json",
"third-party/upstream/**",
"third-party/native-search/licenses/**",
- "third-party/runtime/*.txt"
+ "third-party/runtime/*.txt",
+ "docs/knip/docs/LICENSE",
+ "docs/knip/docs/licenses/**"
]
}
diff --git a/.oxlintrc.json b/.oxlintrc.json
index e581746e..9b712d46 100644
--- a/.oxlintrc.json
+++ b/.oxlintrc.json
@@ -58,6 +58,7 @@
"dist-release",
"packages/ui/src/components/ui",
"packages/ui/src/components/ai-elements",
+ ".claude/skills",
".agents/skills",
"packages/formal-proof",
"apps/ccbuddy-cli",
diff --git a/AGENTS.md b/AGENTS.md
index f920afd7..3dfe8a45 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -54,14 +54,13 @@
- Zustand 状态位于 `packages/ui/src/store/`。广播同步的主题、语言等字段需要防止回环;UI 局部状态不应被误当作服务端事实。
- hooks 中含 JSX 的文件使用 `.tsx`。
-## 进程、协议与远程控制
+## 进程、协议与远程工作区
- Desktop app 通过 stdio 与 Agent 通信。协议改动同步更新 `packages/shared/src/ccbuddy-protocol/index.ts`,提供严格类型与运行时校验。
- Main 负责窗口、原生操作、进程调度和消息转发,不承载 task/session 业务状态。
- 每个窗口使用一个 window-scoped Local Host;本地 workspace 共享该 Host。远程 workspace 由窗口内的连接注册表管理,不另建 Desktop Remote Host。
-- 手机远控连接桌面已有 Host attachment,复用会话运行时;不为手机另起 Agent、Local Host 或远程会话。
-- Desktop 的 `desktop-continuous` 实时链路与手机的 `web-remote-replayable` 恢复链路必须明确区分。修改 stream、snapshot、queue 或重连时,同时验证两种语义。
-- 外部 relay 与 Main 只做鉴权、配对、心跳、转发及 attachment 调度,不保存任务队列、快照等业务状态。
+- Desktop 的 `desktop-continuous` 实时链路与普通 Web、SSH 服务的 `web-remote-replayable` 恢复链路必须明确区分。修改 stream、snapshot、queue 或重连时,同时验证两种语义。
+- Main 仅调度原生窗口、进程和现有 Host attachment,不保存任务队列、快照等业务状态。
- 已接受的 busy/running 输入由 CLI/runtime `CommandInbox` 串行 admission;Renderer 只保留未提交草稿与 pending optimistic overlay,Host owner/lease 负责路由。
- 保留 owner/lease、跨 Host 路由和 stale run 防护,不能仅根据单一路径删除边界判断。
diff --git a/CONTEXT.md b/CONTEXT.md
index 1d828259..21dd15a1 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -1,6 +1,6 @@
# CCbuddy 插件商店(Plugin Store)
-插件设置页及其市场浏览/安装体验的领域词汇表。本文件统一定义商店相关术语,供页面、服务和文档使用。
+插件设置页及其市场浏览/安装体验的领域词汇表。2026-07 商店化重设计引入了一批容易混淆的术语,本文件是唯一裁决来源。
## Language
@@ -8,7 +8,7 @@
**Official Marketplace(官方市场)**:
CCbuddy 官方运营的唯一分发渠道,市场 id 为 `ccbuddy-plugins-official`,内容 = 内置插件 + CDN 插件。是"分发渠道"而非"作者归属"——其中可以收录社区作者的插件。
-_Avoid_: "官方"泛指一切受信市场
+_Avoid_: 把 claude-plugins-official 称作"官方市场";"官方"泛指一切受信市场
**Builtin Plugin(内置插件)**:
随应用包一起分发、启动时播种进官方市场的插件。是官方插件的子集。
@@ -19,8 +19,8 @@ _Avoid_: 预装插件、bundled plugin(口语可用,文档统一"内置")
_Avoid_: 网络插件、在线插件
**Personal Source(个人来源)**:
-用户自行添加的一切插件来源:git/GitHub/URL/本地目录市场、inline 插件。
-_Avoid_: 无
+用户自行添加的一切插件来源:claude-plugins-official、git/GitHub/URL/本地目录市场、inline 插件。
+_Avoid_: 第三方市场(claude-plugins-official 也归此类,"第三方"名不副实)
**Catalog Auto-Refresh(目录自动刷新)**:
进入商店页时对 Official Marketplace 目录的节流后台刷新,用户无感知;只覆盖官方市场。
@@ -44,6 +44,10 @@ _Avoid_: 第三方 tab、我的 tab
公开分段顶部的策展区,名单由官方 CDN 目录的 `featured` 字段远程控制。仅存在于公开分段。
_Avoid_: 与 Recommended 混用
+**Recommended(推荐)**:
+个人分段顶部的策展区,名单由仓内 `recommendedPlugins.json` 维护(当前推 claude-plugins-official 的精选插件)。仅存在于个人分段。
+_Avoid_: 与 Featured 混用
+
**Installed Strip(已安装条)**:
列表页顶部的一排已安装插件图标,点击图标进入详情页。
_Avoid_: 已安装列表(那是 Manage Installed 视图的事)
@@ -63,7 +67,7 @@ _Avoid_: 插件元数据(含糊,可能指 manifest)
_Avoid_: marketplace.json(那是目录,不是清单)
**Example Prompt(示例提示词)**:
-Store Listing 提供的可点击提示词,点击后新建会话并预填(不自动发送)。是详情页唯一的"新建会话"入口。
+Store Listing 提供的可点击提示词,点击后新建会话并预填(不自动发送)。是详情页唯一的"新建会话"入口(原「立即试用」按钮已于 2026-07-14 移除)。
_Avoid_: 快捷指令、prompt 模板、立即试用
### 生命周期状态
diff --git a/DESIGN.md b/DESIGN.md
index 9114feb6..0ed9b97f 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -2,6 +2,11 @@
Portable design system for AI-assisted UI work in this repository.
+This document is the single source of truth for design rules and token intent.
+Theme values are defined in [styles.css](packages/ui/src/styles.css), including the
+fallback themes and active Zai Light / Zai Dark overrides; do not maintain a separate
+copy of their numeric color values in documentation.
+
This file is meant for coding agents. When generating or editing UI in this repo, follow this file before inventing new visual rules.
## Highest-priority UI constraint
@@ -41,6 +46,17 @@ Avoid:
## Theme Modes
+### Full-page desktop overlays
+
+Login, Coding Plan upgrade, and Rewards surfaces must keep minimize, maximize/restore,
+and close-window controls reachable at the top right on Windows and Linux. Reuse
+`DesktopWindowControls` and the existing platform CSS variants. Keep the controls
+outside scrolling content and above page decoration, with a non-draggable hit area.
+The embedded website's close-page action remains separate from close-window.
+macOS keeps native traffic lights; ordinary Web and mobile Web hide desktop controls.
+Verify all three surfaces in both themes, including narrow windows, maximize/restore,
+minimize, and close-page versus close-window behavior.
+
User-facing theme choices are:
- System
@@ -57,6 +73,10 @@ Default light and dark CSS variables still exist as fallback foundations, but ne
Use for key emphasis, important links, active indicators, and brand-accented actions. Never use as a full-page background.
- **Icon Blue**: `--color-icon-blue`
Use for browser-style links and blue icon emphasis that follows the Figma `icon/blue` role. Keep file-type icons on their own descriptor colors.
+- **Icon Purple**: `--color-icon-purple`
+ Use for Highspeed duration emphasis and other UI explicitly mapped to the Figma `icon/purple` role. Do not reuse idle-task or terminal colors directly.
+- **Highspeed Composer Material**: `--color-highspeed-composer-border-idle` with `--color-highspeed-glow-*`
+ Use only for an active Highspeed composer shell. Its stable surface and particle layer are defined by the dedicated Highspeed activation stylesheet and must not leak into ordinary inputs.
- **Accent Surface**: `--color-accent`
Use for weak emphasis blocks, selected highlights, and low-intensity branded surfaces.
- **Background**: `--color-background`
@@ -123,7 +143,7 @@ Use semantic colors only for actual semantic states. Do not borrow success, warn
### Blocking interaction colors
- **Ask Interaction**: `--color-interaction-ask-surface`, `--color-interaction-ask-foreground`, `--color-interaction-ask-fill`
- Legacy compatibility tokens for `AskUserQuestion`; waiting badges no longer use a separate blue treatment.
+ Legacy compatibility surface/foreground tokens for `AskUserQuestion`; waiting badges no longer use a separate blue treatment. The ask fill is reserved for countdown progress.
- **Confirmation Interaction**: `--color-interaction-confirmation-surface`, `--color-interaction-confirmation-foreground`
Use for all waiting badges, including `AskUserQuestion`, permission, and `ExitPlanMode`.
@@ -131,7 +151,8 @@ Waiting badges use one green confirmation treatment so identical waiting copy do
### Workflow timeline colors
-The dynamic-workflow timeline draws with a feature-scoped token family:
+The dynamic-workflow timeline (spec: `docs/dynamic-workflow/presentation.md`) draws with a
+feature-scoped token family:
- **Rule**: `--color-workflow-rule`
Sub-hairline for repeated furniture. Weaker than `--color-border`; do not use it as a
@@ -139,16 +160,22 @@ The dynamic-workflow timeline draws with a feature-scoped token family:
- **Trace / Trace Strong**: `--color-workflow-trace`, `--color-workflow-trace-strong`
Rail and arc stroke ramp: not yet taken vs control has passed. The marching segment
overlays `--color-warning` dashes; there is no third stroke colour.
+- **Fill frame**: a filled hole's frame is drawn in `--color-workflow-trace` (1px), and in
+ `--color-warning` (1.5px) only for the two seconds after the fill joins; never a tinted
+ surface (spec: `docs/dynamic-workflow/presentation.md`, "Holes on the timeline").
- Station lamps and agent pills use the semantic status colours (`--color-success`,
- `--color-warning` for running, `--color-destructive`); agent avatars use the fixed nine-color HEX palette,
+ `--color-warning` for running, `--color-destructive`); agent avatars use the fixed nine-color HEX palette defined in the workflow spec,
assigned by instance index (name hash fallback), and never encode status.
- A compile-feedback row (a script that did not compile, so nothing ran) uses a hollow lamp:
`--color-warning` while it is the latest draft, `--color-foreground-subtlest` once a newer
- draft exists. Never `--color-destructive`, which on this feature belongs to a run that errored.
+ draft exists. Never `--color-destructive`, which on this feature belongs to a run that errored
+ (spec: `docs/dynamic-workflow/presentation.md`, "Compiler feedback").
### Overlay and utility colors
- **Toast**: `--color-toast`
+ The shared toast uses `bg-toast/60` with background blur and a `rounded-2xl` shell.
+ Keep stack positioning in the shared toast component; do not add per-call offsets.
- **Tooltip**: `--color-tooltip`
- **Tooltip Text**: `--color-tooltip-foreground`
- **Tooltip Tag**: `--color-tooltip-tag`
@@ -162,6 +189,39 @@ The dynamic-workflow timeline draws with a feature-scoped token family:
- **Primary Foreground**: `--color-primary-foreground`
- **Secondary**: `--color-secondary`
+### Specialized color tokens
+
+| Token | Purpose |
+| --------------------------------- | ---------------------------------------------------------- |
+| `--color-markdown-inline-code` | Markdown 行内代码背景,沿用 tag / label 胶囊层级 |
+| `--color-popover-header` | Popover header |
+| `--color-tab` | Tab 默认背景 |
+| `--color-tab-active` | 当前激活 tab |
+| `--color-tab-border` | Tab 分隔线 |
+| `--color-feedback-privacy-hint` | 反馈截图隐私提醒文字及叹号图标;独立一行,使用辅助说明字号 |
+| `--color-git-none` | Git 无变更状态,沿用普通文件名颜色 |
+| `--color-git-modified` | Git modified,表示已跟踪文件有未提交修改 |
+| `--color-git-added` | Git added,表示新增文件已进入工作树 / 暂存区 |
+| `--color-git-deleted` | Git deleted,表示已跟踪文件被删除 |
+| `--color-git-renamed` | Git renamed,表示已跟踪文件移动或重命名 |
+| `--color-git-untracked` | Git untracked,表示 Git 尚未跟踪的新文件 |
+| `--color-git-ignored` | Git ignored,继承弱化文本层级 |
+| `--color-git-descendant` | Git descendant,表示目录后代包含变更 |
+| `--color-file-node` | 文件 mention node 背景 |
+| `--color-file-node-hover` | 文件 mention node hover 背景 |
+| `--color-file-node-foreground` | 文件 mention node 文字 / 图标 |
+| `--color-skill-node` | Skill mention node 背景 |
+| `--color-skill-node-hover` | Skill mention node hover 背景 |
+| `--color-skill-node-foreground` | Skill mention node 文字 / 图标 |
+| `--color-command-node` | Command mention node 背景 |
+| `--color-command-node-hover` | Command mention node hover 背景 |
+| `--color-command-node-foreground` | Command mention node 文字 / 图标 |
+
+Git file statuses must use `text-git-*` / `bg-git-*`, rather than borrowing diff or warning tokens.
+Mention tokens belong to prompt-editor and UserPrompt mention nodes; their background, hover,
+and foreground roles must stay together. Markdown inline code uses its own background token,
+currently mapped to `--color-tag`.
+
## Color Usage Rules
- Use semantic tokens, not raw one-off color values.
@@ -178,6 +238,15 @@ The dynamic-workflow timeline draws with a feature-scoped token family:
- `DesktopWindowFrame` uses `bg-background-alt` on macOS desktop. Windows, Linux, and Web keep `bg-background-win-alt`; child surface colors remain independent.
- Linux desktop uses a `16px` outer window-shell radius around the `12px` workspace panels and their `4px` outer inset. Keep the shell radius and compositor clip path equal; maximized windows use `0px`.
+### Lucide 前景透明度
+
+- Lucide 的次级、三级前景使用不透明描边绘制完整图标,再整体与背景混合,避免多条路径交点加深。次级为 60%,三级浅色为 40%、深色为 30%。
+- 保留现有 `text-foreground-subtle` / `text-foreground-subtlest` 调用;同时支持颜色类直接位于图标和从父元素继承。文字继续使用原有半透明颜色。
+- 颜色 utility 的 hover、group-hover、focus、disabled 和 data 状态应同步图标层级;子元素显式使用其他颜色时清除继承的弱化层级。已有 `opacity-*` 继续独立叠加,`opacity-0` 仍隐藏图标。
+- 本次适配范围为 Tailwind 颜色 utility;自定义 CSS / inline 的颜色与滤镜不作为层级入口,不全局改写非 Lucide SVG。标准 Lucide 的 `stroke="currentColor"` 是适配入口。
+- 支持普通百分比颜色修饰符(如 `/50`)以及 `fill-current` 的实心图标。不支持相对颜色语法的浏览器保留原始绘制方式。
+- 验收:390px / 1200px、Zai Light / Zai Dark,分别检查页面、面板和彩色背景上的直接/继承颜色、交点与非交点像素一致、文字颜色不变、交互颜色覆盖和额外透明度。
+
## Typography
### Font families
@@ -187,6 +256,8 @@ The dynamic-workflow timeline draws with a feature-scoped token family:
### UI font tokens
+Each `text-ui-*` utility maps to the corresponding `--text-ui-*` CSS token.
+
All interface typography must use the dedicated `text-ui-*` scale. The Appearance setting controls `--ui-font-size`, whose default is `14px`:
| Token | Formula | Default |
@@ -356,6 +427,11 @@ Control size and primary/secondary action emphasis do not independently change r
- Prefer fluid widths for content containers.
- Fixed widths are acceptable for menus, popovers, dialogs, and stable side panels.
- Avoid arbitrary `w-[...]` and `h-[...]` for ordinary business UI.
+- Prefer the shared Button `size` variants, including `icon*`, over separate width/height overrides. The current variants are `default(h-7)`, `xs(h-5)`, `sm(h-6)`, and `lg(h-8)`; generic control baselines do not replace those component contracts.
+- Regular inputs prefer `h-9`; compact filters and inline inputs may use `h-7` or `h-8`.
+- Use `w-full`, `w-fit`, `max-w-full`, and `min-w-0` for content widths. Fixed overlays may use `w-52`, `w-64`, `w-80`, or `max-w-sm/md/lg/xl` when a stable width is required.
+- Page roots use `h-full` or `min-h-screen`. Nested scroll and preview containers need `min-h-0` and `min-w-0` as appropriate; `overflow-auto` alone does not establish a shrinkable flex region.
+- Prefer `max-h-*` to a fixed height for scrollable content. Do not combine `size-*` with conflicting `w-*` / `h-*` unless a non-square shape is deliberate.
## Components
@@ -484,7 +560,7 @@ Depth rules:
## Workspace layout
-Desktop and wide Web workspace content uses independent conversation, bottom terminal, and Side Pane frames. The conversation frame contains WorkspaceHeader and conversation; the optional terminal has its own frame below it, and Side Pane owns its tab bar. Frames use their own background and border, with 4px resizable gaps matching the macOS outer inset. Resize handles keep a transparent 4px hit area and show a 2px tertiary foreground (`foreground-subtlest/50`) line on hover, focus or drag. The indicator extends along the panel edge, inset by the panel radius at both ends, with rounded ends and no mask. Layout frames do not count toward content radius levels. Mobile remote control retains its single-column and drawer presentation.
+Desktop and wide Web workspace content uses independent conversation, bottom terminal, and Side Pane frames. The conversation frame contains WorkspaceHeader and conversation; the optional terminal has its own frame below it, and Side Pane owns its tab bar. Frames use their own background and border, with 4px resizable gaps matching the macOS outer inset. Resize handles keep a transparent 4px hit area and show a 2px tertiary foreground (`foreground-subtlest/50`) line on hover, focus or drag. The indicator extends along the panel edge, inset by the panel radius at both ends, with rounded ends and no mask. Layout frames do not count toward content radius levels. Mobile remote control retains its single-column and drawer presentation. See [workspace independent panels](docs/design/workspace-independent-panels.md) for platform chrome and persistence boundaries.
## Responsive Behavior
@@ -498,6 +574,23 @@ Rules:
- Prefer changing `max-width`, `grid`, `flex`, and visibility over changing component identity.
- Preserve primary actions at all breakpoints; do not hide core workflows behind desktop-only affordances.
+### Breakpoint roles
+
+| Breakpoint | Preferred use |
+| ---------- | ----------------------------------------------------------------- |
+| `sm:` | Dialog width, button arrangement, small-screen layout corrections |
+| `md:` | Grid columns and form layout expansion |
+| `lg:` | Desktop toolbar visibility and secondary information density |
+| `xl:` | Chat and complex content arrangements |
+| `2xl:` | Wide-screen reading-width caps, not ordinary page behavior |
+
+Start with the unprefixed layout, then add the fewest breakpoints needed. Prefer changing
+layout, `max-width`, and visibility to repeatedly switching fixed dimensions or semantic
+colors, borders, and radii. Keep essential actions available below desktop breakpoints.
+Dialogs should reuse patterns such as `sm:max-w-*`; grids grow from one column rather
+than starting desktop-wide and being repeatedly reduced. Fix unstable base layout before
+adding breakpoint overrides.
+
## Accessibility and Internationalization
- Support keyboard navigation as a first-class interaction path.
diff --git a/apps/ccbuddy-cli/AGENTS.md b/apps/ccbuddy-cli/AGENTS.md
index 0b7e6e62..5f5f7be1 100644
--- a/apps/ccbuddy-cli/AGENTS.md
+++ b/apps/ccbuddy-cli/AGENTS.md
@@ -1,30 +1,25 @@
# Agent 指令
-这里是 TypeScript Node.js Coding Agent CLI,支持主流模型和操作系统。通用工作规则遵循[根 AGENTS.md](../../AGENTS.md);本文件补充 CLI 规则。Node.js 和包管理器版本以仓库根目录的 [mise.toml](../../mise.toml) 与 [package.json](../../package.json) 为准。
+这里是 TypeScript Node.js Coding Agent CLI,支持主流模型和操作系统。通用任务边界、spec、验证和提交条件遵循[根 AGENTS.md](../../AGENTS.md);本文件补充 CLI 特有规则。Node 版本以仓库 [mise.toml](../../mise.toml) 为准。
## 工作规范(最重要)
-- 新增或修改行为前,先编写或更新对应 spec,明确产品规则、状态所有者、接口和验收场景,再实现代码。优先复用现有文档;缺少时按需创建文档及目录,不假定存在固定版本的设计目录。
+- CLI spec 位于 `apps/ccbuddy-cli/docs/design/v2/`;新功能、行为变化先更新,bugfix 和纯重构按根规则判断。下文未带 `apps/ccbuddy-cli/` 的代码与文档路径相对于本目录。
+- 改动任何 tool(新增、删除、schema、权限、执行、prompt、adapter、事件、UI/V4 投影等)之前,必须先阅读并更新 `docs/design/v2/tool/00-tool-change-chain.md`,按端到端链路检查。
- 其次是测试 case 很关键,能证明结果是否符合预期
-- 留好轨迹,包括功能增加后,留下新的文档,bugfix 之后写下 bug 的原因在注释里
+- 功能文档更新到最终行为,复用已有 spec;bugfix 用回归证据留痕,非显然根因和防回归约束在修复点留中文注释。
- agent 友好的项目,留好日志或者接口,让 agent 能完全接手操作
- 长程任务优先:核心 agent loop 默认面向可持续运行的复杂任务设计,不用 tool call 次数做硬停止。资源与安全边界应由 token/context limit 自动 compact、用户取消、权限拒绝、工具超时、输出截断、provider retry 上限等明确条件承担。
- 单个源文件默认不能超过 400 行;超过时必须优先按高内聚低耦合拆分模块,不能用大文件继续堆职责。
- 字符串、数字等常量应提取为命名变量或常量,不要在业务逻辑中直接散落字面量,便于一处修改、统一维护。
-- 修改数据库结构前,应与模块维护者确认方案,明确 migration、兼容性和回滚策略。
+- 修改数据库结构时必须同步 CLI 负责人,仔细设计 migration
- 键盘操作优先,核心逻辑都可以走键盘操作。鼠标操作是增益能力
## 工具规范
- 与操作系统交互之前,需要考虑同时支持 windows、mac、linux
- 保持默认的发布路径为标准 Node.js CLI 打包方式。
-- 项目自有的环境变量统一使用 `CCBUDDY_` 前缀命名,但不要随便新增环境变量;新增前必须先在对应功能的 spec 中定义用途、优先级、错误行为和测试覆盖,能用配置文件、CLI 参数或 session 配置表达的能力,优先不要做成环境变量。
-
-## 开源内容与敏感信息
-
-- 项目许可与归属声明见仓库根目录的 [LICENSE](../../LICENSE)、[NOTICE.md](../../NOTICE.md) 和 [THIRD-PARTY-NOTICES.md](../../THIRD-PARTY-NOTICES.md)。引入第三方代码、文档、提示词或素材前,应确认来源、许可证和使用权限,按适用许可保留版权、署名及修改说明;不得为开源清理而删除仍适用的归属声明。
-- 文档、示例、测试数据、日志和提交信息不得包含真实凭据、用户隐私、内部服务地址、个人工作目录或未获授权公开的内容;示例使用虚构数据和占位值。
-- 发布前应核对实际交付范围;包含 Git 历史时,也应检查历史内容。当前文件中的删除或替换不代表历史记录已清理。
+- 项目自有的环境变量统一使用 `CCBUDDY_` 前缀命名,但不要随便新增环境变量;新增前必须先在 `docs/design/v2` 的 spec 中定义用途、优先级、错误行为和测试覆盖,能用配置文件、CLI 参数或 session 配置表达的能力,优先不要做成环境变量。
## 跨平台兼容原则
@@ -102,6 +97,7 @@
## 验证
-- 在完成代码变更之前,从仓库根目录运行 `pnpm typecheck` 和 `pnpm lint`;涉及 CLI 代码时,还应运行 `pnpm --dir apps/ccbuddy-cli typecheck` 和 `pnpm --dir apps/ccbuddy-cli lint`。
-- 测试入口以目标包当前的 `package.json` 和实际测试文件为准,不假定存在统一的测试命令;行为变更应执行对应测试,交互变更应覆盖 E2E 场景。
-- 如实记录执行过的命令、结果和未验证范围;缺少测试入口、已有失败或环境限制不得写成通过。
+以下命令从 ccbuddy 仓库根目录执行;若当前已在 `apps/ccbuddy-cli`,省略 `--dir apps/ccbuddy-cli`。
+
+- CLI 代码变更除根验证矩阵外,运行 `pnpm --dir apps/ccbuddy-cli typecheck`、`pnpm --dir apps/ccbuddy-cli lint`,以及受影响 CLI 包与调用方的测试。用 CLI 自身的 package scripts / Turbo 选择范围,不能用根 `pnpm test:unit:affected` 代替。
+- 无法可靠确定影响范围,或修改 CLI 公共配置、测试基础设施时,运行 `pnpm --dir apps/ccbuddy-cli test`。必要检查通过后不重复运行;纯文档、环境阻塞、提交条件均遵循根规则。
diff --git a/apps/ccbuddy-cli/README.md b/apps/ccbuddy-cli/README.md
index 20557e37..21f10690 100644
--- a/apps/ccbuddy-cli/README.md
+++ b/apps/ccbuddy-cli/README.md
@@ -40,7 +40,7 @@ Run `npm run bootstrap` after cloning the repository. It checks the local Node.j
## Plugin Development
-ccbuddy plugins are local bundles that can contribute skills, custom commands, and MCP servers.
+ccbuddy plugins are local bundles that can contribute skills, custom commands, and MCP servers. The plugin surface is compatible with the Claude Code plugin layout, so existing plugin content can be reused with minimal changes.
Plugin state lives under `~/.ccbuddy/cli/plugins`:
@@ -69,9 +69,16 @@ For local plugin development, put the plugin in any directory, then add it to th
}
```
-### Plugin Manifest
+### Manifest Compatibility
-MCP config can live directly in `.ccbuddy-plugin/plugin.json` through `mcpServers`. A plugin may provide both `.mcp.json` and manifest `mcpServers`; when the same server name appears in both places, `mcpServers` from the selected manifest wins.
+ccbuddy discovers the first manifest that exists:
+
+1. `.ccbuddy-plugin/plugin.json`
+2. `.claude-plugin/plugin.json`
+
+`.ccbuddy-plugin/plugin.json` uses the same field names as Claude Code. Use it only when ccbuddy needs different metadata or runtime wiring. If the Claude manifest already works, a plugin can ship only `.claude-plugin/plugin.json`.
+
+MCP config can live directly in `.ccbuddy-plugin/plugin.json` through `mcpServers`. This is useful when the Claude-compatible `.mcp.json` points at a different runtime, such as Bun source files, while ccbuddy should run a built Node.js server from `dist/`. A plugin may provide both `.mcp.json` and manifest `mcpServers`; when the same server name appears in both places, `mcpServers` from the selected manifest wins.
Supported fields in the current ccbuddy plugin surface:
@@ -109,13 +116,28 @@ Example `.ccbuddy-plugin/plugin.json` with inline MCP config:
}
```
+ccbuddy also reads plugin `.mcp.json` using Claude Code's wrapper shape. Use this when the same MCP config should be shared by Claude Code and ccbuddy:
+
+```json
+{
+ "mcpServers": {
+ "my-server": {
+ "command": "node",
+ "args": ["${CLAUDE_PLUGIN_ROOT}/dist/mcp/server.js"]
+ }
+ }
+}
+```
+
+Unsupported Claude plugin fields are ignored with diagnostics rather than executed. This includes `hooks`, `agents`, `outputStyles`, `lspServers`, `channels`, and `dependencies`.
+
### Variables
-Plugin MCP config can use these variable names:
+Plugin MCP config can use both ccbuddy and Claude-compatible variable names:
-- `${CCBUDDY_PLUGIN_ROOT}`
-- `${CCBUDDY_PLUGIN_DATA}`
-- `${CCBUDDY_PROJECT_DIR}`
+- `${CCBUDDY_PLUGIN_ROOT}` and `${CLAUDE_PLUGIN_ROOT}`
+- `${CCBUDDY_PLUGIN_DATA}` and `${CLAUDE_PLUGIN_DATA}`
+- `${CCBUDDY_PROJECT_DIR}` and `${CLAUDE_PROJECT_DIR}`
- `${user_config.key}`
- `${CCBUDDY_SOME_ENV}`
@@ -125,6 +147,7 @@ Only environment variables with the `CCBUDDY_` prefix are expanded. Missing vari
```txt
my-plugin/
+ .claude-plugin/plugin.json
.ccbuddy-plugin/plugin.json
.mcp.json
skills/
@@ -134,7 +157,7 @@ my-plugin/
src/
```
-For MCP servers, prefer Node's normal package build and `bin` output when targeting ccbuddy-cli, and keep all process/file/network side effects inside the MCP server boundary.
+Keep reusable content in Claude-compatible locations. Add a `.ccbuddy-plugin/plugin.json` only for ccbuddy-specific package names, built artifacts, or command paths. For MCP servers, prefer Node's normal package build and `bin` output when targeting ccbuddy-cli, and keep all process/file/network side effects inside the MCP server boundary.
## MCP Configuration
diff --git a/apps/ccbuddy-cli/dependencies/README.md b/apps/ccbuddy-cli/dependencies/README.md
index 621d91c6..2ac3897d 100644
--- a/apps/ccbuddy-cli/dependencies/README.md
+++ b/apps/ccbuddy-cli/dependencies/README.md
@@ -15,9 +15,10 @@ macOS metadata (`__MACOSX`, `._*`, `.DS_Store`) is excluded.
The corresponding license texts and version/source inventory are maintained in
[`third-party/native-search`](../../../third-party/native-search) and included in
the root third-party notices. Preparation also writes complete notices and a
-source manifest beside each binary, including cache hits. Newly produced tar/zip
-archives carry these files; do not redistribute the original input archives
-without the companion notices. See the [maintenance guide](../../../third-party/README.md).
+source manifest beside each binary, including cache hits. Producer archives keep
+only the binary so that they stay byte-identical to the pinned SHA-256; do not
+redistribute the original input archives without the companion notices. See the
+[maintenance guide](../../../third-party/README.md).
The active releases and SHA-256 pins are defined in
[`scripts/native-search-tools-config.mjs`](../../../scripts/native-search-tools-config.mjs)
@@ -35,10 +36,14 @@ Remote packaging uses `resolveRemoteNativeSearchPrebuiltPlan` to retain the
deployed macOS rg13 contract. Its component versions come from the same plan as
the extracted archives; the default Desktop / SEA / server-cli plan continues to use rg14.
-Desktop, CLI SEA, server-cli staging and remote asset packaging resolve these
+Desktop, CLI SEA, server-cli staging and remote asset packaging prepare native
+search tools through `scripts/prepare-native-search-tools.mjs`. When a dependency
+mirror is configured (`NATIVE_SEARCH_TOOLS_DOWNLOAD_BASE_URL`, or a resolvable
+`CCBUDDY_DEPS_BASE_URL` / `INTRANET_MACHINE_HOST`), it downloads the same archives
+from that mirror with the same SHA-256 checks. Otherwise it resolves these
archives relative to the repository, independently of the current working
-directory. Native search preparation does not download archives or fall back to
-a mirror. Other build dependencies retain their own preparation steps.
+directory, and never downloads. The two sources are not fallbacks for each other.
+Other build dependencies retain their own preparation steps.
Server-cli staging prepares its own checked cache instead of reusing remote tool
directories, which may contain the macOS rg13 release.
@@ -54,8 +59,8 @@ node scripts/prepare-native-search-tools.mjs --platform linux --arch x64 --outpu
# Package the CLI, including the prepared target tools.
pnpm build:sea
-# Verify archives, server-cli staging, SEA assets, and remote component packaging.
-node --test scripts/native-search-tools.test.mjs
+# Verify archive extraction from this directory and the source selection.
+pnpm exec vitest run packages/desktop/test/prepare-native-search-tools.test.ts
```
Preparation checks all selected archive hashes before reusing or replacing any
diff --git a/apps/ccbuddy-cli/docs/design/v2/runtime/bootstrap-contract-integration.md b/apps/ccbuddy-cli/docs/design/v2/runtime/bootstrap-contract-integration.md
new file mode 100644
index 00000000..78cccdef
--- /dev/null
+++ b/apps/ccbuddy-cli/docs/design/v2/runtime/bootstrap-contract-integration.md
@@ -0,0 +1,37 @@
+# Bootstrap interface integration
+
+The existing bootstrap implementation and current contracts remain the source
+of truth for application options and workflow read results. The integration
+must keep these public interfaces aligned with the upstream v3.15.1 call sites:
+
+- The Host may inject the subagent runtime configuration reader; bootstrap
+ passes the Host snapshot to runtime instead of reading a second configuration
+ owner. Ordinary CLI use retains startup configuration when the reader is absent.
+- Protocol Hosts may inject provider endpoint routing, topic resources and the
+ visualize capability flag through `CCbuddyAppOptions`. The protocol entrypoint
+ accepts the existing routing port override. These declarations do not create
+ new configuration owners or account-backed provider behavior.
+- Workflow journal reads return the current `DynamicWorkflowRunEventsResult`
+ and `DynamicWorkflowRunArtifactItemsResult` contracts, including `hasMore`.
+ Event pages preserve the count and byte limits; artifact item pages preserve
+ the field selection and byte limit. The bridge forwards the existing result
+ without re-deriving pagination facts.
+
+The application typecheck and actual CLI build are the regression gate for
+these declaration-only repairs; they previously failed where implementation
+and public interfaces diverged.
+
+## CLI provider and debug integration
+
+The prompt command does not attach the upstream CDN refresh reporter: CCbuddy's
+provider entrypoint resolves a local bundled template as required by
+`byok-only.md`. The current legacy personal-config import remains attached.
+The actual CLI bundle is the regression gate for stale imports of removed
+upstream capabilities.
+
+The debug analyzer is the existing read-only aggregation point for SQLite,
+event and log observations. Timeline, span, context and cache projections keep
+their shared correlation rules in this module for the integration. A scoped
+`max-lines` exception documents that aggregation responsibility; other lint
+rules and the rest of the debug package remain checked. New independent
+analysis responsibilities should use adjacent modules.
diff --git a/apps/ccbuddy-cli/docs/design/v2/runtime/compact-request-context.md b/apps/ccbuddy-cli/docs/design/v2/runtime/compact-request-context.md
new file mode 100644
index 00000000..d664e4b2
--- /dev/null
+++ b/apps/ccbuddy-cli/docs/design/v2/runtime/compact-request-context.md
@@ -0,0 +1,26 @@
+# Compact request context preservation
+
+The session runtime owns the task type. Speculative compaction derives
+`modelRequestSessionType` from that type and supplies it to the shared compact
+model request helper. The helper forwards it through `ModelInvocationContext`
+to the provider adapter for both the original stream and the existing HTTP
+fallback. Trace attribution remains attached to the same request. The fallback
+creates a replacement logical call linked to its predecessor.
+
+```mermaid
+sequenceDiagram
+ participant Runtime as Session runtime
+ participant Compact as Speculative compact helper
+ participant Context as ModelInvocationContext
+ participant Adapter as Provider adapter
+ Runtime->>Compact: task-derived session type and trace
+ Compact->>Context: Preserve request attribution
+ Context->>Adapter: Streaming attempt
+ Adapter-->>Compact: Failure before content commit
+ Compact->>Context: Same attribution and replacement call link
+ Context->>Adapter: Existing HTTP fallback
+```
+
+Integration regression evidence is in `compact-request-context.test.ts`: the
+provider-side stream and fallback observe the exact supplied session type and
+trace. The helper must not erase the field merely to satisfy a typecheck.
diff --git a/apps/ccbuddy-cli/docs/design/v2/tool/00-tool-change-chain.md b/apps/ccbuddy-cli/docs/design/v2/tool/00-tool-change-chain.md
new file mode 100644
index 00000000..4b5ef3f5
--- /dev/null
+++ b/apps/ccbuddy-cli/docs/design/v2/tool/00-tool-change-chain.md
@@ -0,0 +1,52 @@
+# Tool change chain
+
+## Current execution order
+
+The runtime owns the logical workspace identity and supplies it through
+`ToolExecutorDeps`. Tool input resolution and approval adjustments share
+`ToolInputResolutionContext`; they must preserve the same identity used by the
+handler and permission service. The storage contract selects project state by
+`workspaceIdentity?.trim() || workspacePath` and keeps it under the CCbuddy data
+root. A physical working directory remains the base for ordinary file paths.
+
+```mermaid
+sequenceDiagram
+ participant Runtime as Session runtime
+ participant Executor as Tool executor
+ participant Resolve as Input resolution
+ participant Permission as Permission service
+ participant Handler as Tool handler
+ participant Store as Workspace state store
+ Runtime->>Executor: workingDirectory and workspaceIdentity
+ Executor->>Resolve: Shared resolution context
+ Resolve->>Store: Read saved definition and write scoped draft
+ Resolve-->>Executor: Resolved bytes
+ Executor->>Permission: Resolved input and same workspace identity
+ Permission-->>Executor: Approval or scoped draft exemption
+ Executor->>Resolve: Approved adjustments with same context
+ Executor->>Handler: Approved bytes and same workspace identity
+ Handler->>Store: Persist or execute in the same logical scope
+```
+
+## Integration regression constraints
+
+- `CreateWorkflow` and `SaveWorkflow` resolve project definitions using the
+ session identity. Two identities at the same physical path have separate
+ definitions, overwrite checks, drafts and run lookup results.
+- `Edit` and `Write` are exempt from confirmation only for files under the
+ current logical workspace's application-owned `workflow-drafts` directory.
+ Other identities' drafts and project-local lookalike directories require the
+ ordinary permission flow. Running a workflow retains its execution approval.
+- Missing identity keeps the existing path fallback. Global workflow storage
+ stays global.
+
+## Required evidence
+
+`workflow-data-paths.test.ts` covers data-root placement and scoped draft
+approval. `workflow-run-identity.test.ts` covers save/find-from-run isolation.
+`workflow-tool-resolution-identity.test.ts` exercises the real executor context
+builder and CreateWorkflow resolution against two scoped definitions.
+
+For a tool change, inspect its schema, resolution hook, permission decision,
+approval adjustments, handler, persistence, session events and client
+projection. Update the affected contracts and tests before implementation.
diff --git a/apps/ccbuddy-cli/package.json b/apps/ccbuddy-cli/package.json
index 7cf1f171..ce0db89d 100644
--- a/apps/ccbuddy-cli/package.json
+++ b/apps/ccbuddy-cli/package.json
@@ -7,11 +7,12 @@
"scripts": {
"build": "turbo run build",
"build:sea": "pnpm --filter @ccbuddy/cli build:sea",
- "check": "pnpm registry:check && turbo run typecheck",
+ "check": "pnpm registry:check && turbo run typecheck && pnpm test",
"clean": "turbo run clean",
"cli:build": "pnpm --filter @ccbuddy/cli build",
"cli:dev": "pnpm --filter @ccbuddy/cli dev",
"cli:upload-sea": "pnpm --filter @ccbuddy/cli upload:sea",
+ "coverage": "turbo run coverage",
"dev": "pnpm --filter @ccbuddy/cli dev",
"format": "oxfmt --write package.json \"packages/**/*.json\" \"packages/**/*.ts\" \"packages/**/*.mjs\"",
"format:check": "oxfmt --check package.json \"packages/**/*.json\" \"packages/**/*.ts\" \"packages/**/*.mjs\"",
@@ -20,11 +21,16 @@
"lint:fix": "turbo run lint:fix",
"prompt-trajectory:model-io": "pnpm --filter @ccbuddy/prompt-trajectory model-io --",
"prompt-trajectory:prompt": "pnpm --filter @ccbuddy/prompt-trajectory record:prompt --",
+ "prompt-trajectory:testcases": "pnpm --filter @ccbuddy/prompt-trajectory run:testcases --",
"registry:check": "node scripts/generate-bash-command-registry.mjs --check",
"registry:generate": "node scripts/generate-bash-command-registry.mjs",
"release": "node ../../node_modules/release-it/bin/release-it.js --config .release-it.json",
"release:dry": "pnpm run release --release-version",
"sea": "pnpm --filter @ccbuddy/cli sea",
+ "test": "turbo run test",
+ "test:e2e:memory": "pnpm --dir e2e/memory run e2e:build",
+ "test:browser-headless": "pnpm --filter @ccbuddy/browser-use-plugin... build && pnpm --filter @ccbuddy/browser-use-plugin exec vitest run test/headless-browser.e2e.test.ts",
+ "test:browser-headless:linux": "docker build --progress=plain -f packages/browser-use-plugin/e2e/headless-linux.Dockerfile ../..",
"typecheck": "turbo run typecheck"
},
"dependencies": {
@@ -34,6 +40,7 @@
"devDependencies": {
"@withfig/autocomplete": "2.692.3",
"@types/node": "^24.0.0",
+ "@vitest/coverage-v8": "^4.1.5",
"c8": "^11.0.0",
"esbuild": "^0.25.0",
"oxfmt": "^0.47.0",
@@ -41,7 +48,8 @@
"postject": "^1.0.0-alpha.6",
"tsx": "^4.20.0",
"turbo": "^2.4.0",
- "typescript": "^5.9.0"
+ "typescript": "^5.9.0",
+ "vitest": "^4.1.5"
},
"engines": {
"node": "24.14.0"
diff --git a/apps/ccbuddy-cli/packages/adapters/package.json b/apps/ccbuddy-cli/packages/adapters/package.json
index ef581550..b06ade99 100644
--- a/apps/ccbuddy-cli/packages/adapters/package.json
+++ b/apps/ccbuddy-cli/packages/adapters/package.json
@@ -94,6 +94,8 @@
"scripts": {
"build": "tsc",
"clean": "node ../../scripts/clean-dist.mjs",
+ "test": "vitest tests",
+ "coverage": "vitest run tests --coverage",
"typecheck": "tsc --noEmit",
"lint": "oxlint src",
"lint:fix": "oxlint --write src"
@@ -124,6 +126,7 @@
"devDependencies": {
"@types/node": "^24.0.0",
"@types/semver": "^7.7.0",
- "@types/yauzl": "^2.10.3"
+ "@types/yauzl": "^2.10.3",
+ "vitest": "^4.1.5"
}
}
diff --git a/apps/ccbuddy-cli/packages/adapters/src/auth/coding-plan-api-key.ts b/apps/ccbuddy-cli/packages/adapters/src/auth/coding-plan-api-key.ts
index 2353b0b3..8654d4cd 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/auth/coding-plan-api-key.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/auth/coding-plan-api-key.ts
@@ -1,266 +1,233 @@
-import type { HttpClientPort, HttpClientRunOptions, TraceContext } from "@ccbuddy/contracts";
-import { resolveBigModelApiOrigin } from "@ccbuddy/shared";
-
+import { AsyncLocalStorage } from "node:async_hooks";
+import {
+ isHttpClientPortError,
+ type HttpClientPort,
+ type HttpClientRunOptions,
+ type TraceContext,
+} from "@ccbuddy/contracts";
+import {
+ ProjectAccessTokenClient,
+ ProjectAccessTokenTransientError,
+ resolveBigModelApiOrigin,
+ type ProjectAccessTokenMaterial,
+} from "@ccbuddy/shared";
+import { ZaiBusinessTokenCache, type ZaiBusinessToken } from "./zai-business-token-cache.js";
+
+const BUSINESS_AUTH_MAX_ATTEMPTS = 2;
+const SINGLE_ATTEMPT = 1;
+const PROJECT_TOKEN_REQUEST_TIMEOUT_MS = 15_000;
const ZAI_API_HOST = "https://api.z.ai";
-const JSON_CONTENT_TYPE = "application/json";
-const CCBUDDY_API_KEY_NAME = "ccbuddy-api-key";
-const DEFAULT_ORG_NAME = "默认机构";
-const DEFAULT_PROJECT_NAME = "默认项目";
-
export type CodingPlanFamily = "bigmodel" | "zai";
-
export interface CodingPlanApiKeyResolverOptions {
httpClient: HttpClientPort;
trace?: TraceContext;
+ locationStore?: {
+ load(key: string): Promise;
+ save(key: string, value: string): Promise;
+ delete(key: string): Promise;
+ };
+ observe?(event: { family: string; stage: "resolve"; code: string }): void;
}
-
export interface ResolveCodingPlanApiKeyInput {
accessToken: string;
family: CodingPlanFamily;
+ accountIdentity?: string;
+ rejectedProjectTokenFingerprint?: string;
+ trace?: TraceContext;
}
-
export interface CodingPlanApiKeyResolver {
resolve(input: ResolveCodingPlanApiKeyInput, options?: HttpClientRunOptions): Promise;
+ resolveMaterial(
+ input: ResolveCodingPlanApiKeyInput,
+ options?: HttpClientRunOptions,
+ ): Promise;
+ clear(): void;
}
-
export class CodingPlanApiKeyError extends Error {
- constructor(message: string, options: { cause?: unknown } = {}) {
- super(message, options);
+ constructor(message: string) {
+ super(message);
this.name = "CodingPlanApiKeyError";
}
}
-interface RemoteEnvelope {
- code?: number | string;
- data?: T;
- msg?: string;
-}
-
-interface RemoteProjectInfo {
- projectId?: string;
- projectName?: string;
-}
-
-interface RemoteOrganizationInfo {
- organizationId?: string;
- organizationName?: string;
- projects?: RemoteProjectInfo[];
-}
-
-interface RemoteCustomerInfo {
- organizations?: RemoteOrganizationInfo[];
-}
-
-interface RemoteApiKeySummary {
- apiKey?: string;
- name?: string;
-}
-
-interface RemoteApiKeySecret {
- secretKey?: string;
-}
-
-interface RemoteZaiBizToken {
- access_token?: string;
- accessToken?: string;
-}
-
+/** CLI 进程内唯一换证入口;保留旧导出名兼容调用方,返回值已是短期 Token。 */
export function createCodingPlanApiKeyResolver(
options: CodingPlanApiKeyResolverOptions,
): CodingPlanApiKeyResolver {
- return {
- async resolve(
- input: ResolveCodingPlanApiKeyInput,
- runOptions?: HttpClientRunOptions,
- ): Promise {
- const accessToken = input.accessToken.trim();
- if (!accessToken) {
- throw new CodingPlanApiKeyError("OAuth access token is required.");
- }
-
- if (input.family === "bigmodel") {
- return resolveBizApiKey(
- {
- authorization: accessToken,
- host: resolveBigModelApiOrigin(process.env),
- httpClient: options.httpClient,
- trace: options.trace,
- },
- runOptions,
- );
- }
-
- const bizToken = await resolveZaiBizToken(options, accessToken, runOptions);
- return resolveBizApiKey(
+ const execution = new AsyncLocalStorage<{
+ context?: HttpClientRunOptions["context"];
+ trace?: TraceContext;
+ business?: ZaiBusinessToken;
+ }>();
+ const businessTokens = new ZaiBusinessTokenCache(() =>
+ options.observe?.({
+ family: "zai",
+ stage: "resolve",
+ code: "project_token_business_login_refresh_deferred",
+ }),
+ );
+ const request = async (url: string, init: RequestInit) => {
+ const response = await options.httpClient
+ .request(
{
- authorization: `Bearer ${bizToken}`,
- host: ZAI_API_HOST,
- httpClient: options.httpClient,
- requireSecretKey: true,
- trace: options.trace,
+ url,
+ method: init.method === "POST" ? "POST" : "GET",
+ headers: Object.fromEntries(new Headers(init.headers)),
+ ...(typeof init.body === "string" ? { body: new TextEncoder().encode(init.body) } : {}),
+ maxResponseBytes: 64 * 1024,
+ timeoutMs: PROJECT_TOKEN_REQUEST_TIMEOUT_MS,
+ trace: execution.getStore()?.trace ?? options.trace,
},
- runOptions,
- );
- },
+ { context: execution.getStore()?.context },
+ )
+ .catch((error: unknown) => {
+ if (
+ isHttpClientPortError(error) &&
+ (error.code === "network_error" || error.code === "timeout")
+ )
+ throw new ProjectAccessTokenTransientError();
+ throw error;
+ });
+ if (response.status === 401 && new URL(url).pathname.startsWith("/api/biz/")) {
+ const run = execution.getStore();
+ if (
+ run?.business &&
+ new Headers(init.headers).get("Authorization") === `Bearer ${run.business.token}`
+ )
+ businessTokens.invalidate(run.business);
+ }
+ if (response.status === 404) return { code: 404 };
+ if (response.status === 429 || (response.status >= 500 && response.status < 600))
+ throw new ProjectAccessTokenTransientError();
+ if (response.status < 200 || response.status >= 300)
+ throw new CodingPlanApiKeyError("project_token_request_failed");
+ return JSON.parse(new TextDecoder().decode(response.body)) as unknown;
};
-}
-
-async function resolveZaiBizToken(
- options: CodingPlanApiKeyResolverOptions,
- oauthAccessToken: string,
- runOptions?: HttpClientRunOptions,
-): Promise {
- const payload = await requestRemoteData(
- options.httpClient,
- {
- body: new TextEncoder().encode(JSON.stringify({ token: oauthAccessToken })),
- headers: {
- "Content-Type": JSON_CONTENT_TYPE,
- },
- method: "POST",
- trace: options.trace,
- url: `${ZAI_API_HOST}/api/auth/z/login`,
- },
- runOptions,
- );
- const token = payload?.access_token?.trim() ?? payload?.accessToken?.trim() ?? "";
- if (!token) {
- throw new CodingPlanApiKeyError("Z.AI biz token response is missing access_token.");
- }
- return token;
-}
-
-async function resolveBizApiKey(
- input: {
- authorization: string;
- host: string;
- httpClient: HttpClientPort;
- requireSecretKey?: boolean;
- trace?: TraceContext;
- },
- runOptions?: HttpClientRunOptions,
-): Promise {
- const customerInfo = await requestRemoteData(
- input.httpClient,
- {
- headers: createBizAuthHeaders(input.authorization),
- method: "GET",
- trace: input.trace,
- url: `${input.host}/api/biz/customer/getCustomerInfo`,
- },
- runOptions,
- );
- const location = pickOrgAndProject(customerInfo);
- if (!location) {
- throw new CodingPlanApiKeyError("Unable to resolve organization and project.");
- }
-
- const listUrl =
- `${input.host}/api/biz/v1/organization/${location.organizationId}` +
- `/projects/${location.projectId}/api_keys`;
- const keys =
- (await requestRemoteData(
- input.httpClient,
- {
- headers: createBizAuthHeaders(input.authorization),
- method: "GET",
- trace: input.trace,
- url: listUrl,
- },
- runOptions,
- )) ?? [];
- const keyEntry =
- keys.find((item) => item.name === CCBUDDY_API_KEY_NAME) ??
- (await requestRemoteData(
- input.httpClient,
- {
- body: new TextEncoder().encode(JSON.stringify({ name: CCBUDDY_API_KEY_NAME })),
- headers: createBizAuthHeaders(input.authorization),
- method: "POST",
- trace: input.trace,
- url: listUrl,
- },
- runOptions,
- ));
- const apiKey = keyEntry?.apiKey?.trim() ?? "";
- if (!apiKey) {
- throw new CodingPlanApiKeyError("API key response is missing apiKey.");
- }
-
- const secret = await requestRemoteData(
- input.httpClient,
- {
- headers: createBizAuthHeaders(input.authorization),
- method: "GET",
- trace: input.trace,
- url: `${listUrl}/copy/${encodeURIComponent(apiKey)}`,
- },
- runOptions,
- );
- const secretKey = secret?.secretKey?.trim() ?? "";
- if (!secretKey) {
- if (input.requireSecretKey) {
- throw new CodingPlanApiKeyError("API key copy response is missing secretKey.");
+ const client = new ProjectAccessTokenClient({
+ request,
+ observe: options.observe,
+ locationStore: options.locationStore,
+ });
+ let generation = 0;
+ const resolveMaterial = async (
+ input: ResolveCodingPlanApiKeyInput,
+ ): Promise => {
+ const oauth = input.accessToken.trim();
+ if (!oauth) throw new CodingPlanApiKeyError("project_token_login_required");
+ const capturedGeneration = generation;
+ const maxAttempts = input.family === "zai" ? BUSINESS_AUTH_MAX_ATTEMPTS : SINGLE_ATTEMPT;
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
+ if (generation !== capturedGeneration)
+ throw new CodingPlanApiKeyError("project_token_scope_invalidated");
+ const business =
+ input.family === "zai"
+ ? await businessTokens
+ .resolve(oauth, () =>
+ request(`${ZAI_API_HOST}/api/auth/z/login`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ token: oauth }),
+ }),
+ )
+ .catch(() => {
+ options.observe?.({
+ family: input.family,
+ stage: "resolve",
+ code: "project_token_login_failed",
+ });
+ throw new CodingPlanApiKeyError("project_token_login_failed");
+ })
+ : undefined;
+ if (generation !== capturedGeneration)
+ throw new CodingPlanApiKeyError("project_token_scope_invalidated");
+ const loginToken = business?.token ?? oauth;
+ const run = execution.getStore();
+ if (run) run.business = business;
+ try {
+ const result = await client.resolve({
+ origin: input.family === "zai" ? ZAI_API_HOST : resolveBigModelApiOrigin(process.env),
+ family: input.family,
+ loginToken,
+ accountId: input.accountIdentity ?? input.family,
+ personalProjectSelection: "default",
+ rejectedProjectTokenFingerprint: input.rejectedProjectTokenFingerprint,
+ });
+ if (generation !== capturedGeneration)
+ throw new CodingPlanApiKeyError("project_token_scope_invalidated");
+ return result;
+ } catch (error) {
+ // 只恢复业务 JWT 的明确 HTTP 401;不把模型 401、403 或网络故障猜成过期。
+ if (!business || !businessTokens.wasRejected(business)) throw error;
+ businessTokens.invalidate(business);
+ if (attempt + 1 === maxAttempts) throw error;
+ options.observe?.({
+ family: input.family,
+ stage: "resolve",
+ code: "project_token_business_login_refresh",
+ });
+ }
}
- return apiKey;
- }
-
- return `${apiKey}.${secretKey}`;
-}
-
-async function requestRemoteData(
- httpClient: HttpClientPort,
- request: Parameters[0],
- options?: HttpClientRunOptions,
-): Promise {
- const response = await httpClient.request(
- {
- maxResponseBytes: 64 * 1024,
- ...request,
- },
- options,
- );
- const parsed = JSON.parse(new TextDecoder().decode(response.body)) as RemoteEnvelope;
- if (!isSuccessfulRemoteCode(parsed.code)) {
- throw new CodingPlanApiKeyError(parsed.msg ?? `Remote business error ${parsed.code}`);
- }
- return parsed.data ?? null;
-}
-
-function createBizAuthHeaders(authorization: string): Record {
- return {
- Authorization: authorization,
- "Content-Type": JSON_CONTENT_TYPE,
+ throw new CodingPlanApiKeyError("project_token_request_failed");
};
-}
-
-function pickOrgAndProject(customerInfo: RemoteCustomerInfo | null): {
- organizationId: string;
- projectId: string;
-} | null {
- const organizations = customerInfo?.organizations ?? [];
- const org =
- organizations.find((item) => item.organizationName?.includes(DEFAULT_ORG_NAME)) ??
- organizations[0];
- const projects = org?.projects ?? [];
- const project =
- projects.find((item) => item.projectName?.includes(DEFAULT_PROJECT_NAME)) ?? projects[0];
- if (!org?.organizationId || !project?.projectId) {
- return null;
- }
+ const resolveForCaller = (
+ input: ResolveCodingPlanApiKeyInput,
+ runOptions?: HttpClientRunOptions,
+ ) =>
+ waitForProjectToken(
+ () =>
+ execution.run(
+ {
+ // 单飞属于鉴权 owner,不继承会话 signal 或 context.abortSignal;只保留观测上下文。
+ context: runOptions?.context
+ ? { trace: runOptions.context.trace, logger: runOptions.context.logger }
+ : undefined,
+ trace: input.trace ?? runOptions?.context?.trace,
+ },
+ () => resolveMaterial(input),
+ ),
+ runOptions?.signal,
+ );
return {
- organizationId: org.organizationId,
- projectId: project.projectId,
+ resolveMaterial: resolveForCaller,
+ async resolve(input, runOptions) {
+ const value = await resolveForCaller(input, runOptions);
+ return value.token;
+ },
+ clear() {
+ generation++;
+ businessTokens.clear();
+ client.clear();
+ },
};
}
-function isSuccessfulRemoteCode(code: unknown): boolean {
- return (
- code === undefined ||
- code === null ||
- code === 0 ||
- code === 200 ||
- code === "0" ||
- code === "200"
- );
+/** 仅取消当前调用方的等待;底层 Promise 始终有拒绝处理器,不误伤共享换证。 */
+async function waitForProjectToken(run: () => Promise, signal?: AbortSignal): Promise {
+ if (!signal) return run();
+ signal.throwIfAborted();
+ const value = await new Promise((resolve, reject) => {
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
+ const onAbort = () => {
+ cleanup();
+ reject(signal.reason);
+ };
+ signal.addEventListener("abort", onAbort, { once: true });
+ Promise.resolve()
+ .then(() => {
+ signal.throwIfAborted();
+ return run();
+ })
+ .then(
+ (result) => {
+ cleanup();
+ resolve(result);
+ },
+ (error: unknown) => {
+ cleanup();
+ reject(error);
+ },
+ );
+ });
+ signal.throwIfAborted();
+ return value;
}
diff --git a/apps/ccbuddy-cli/packages/adapters/src/auth/zai-business-token-cache.ts b/apps/ccbuddy-cli/packages/adapters/src/auth/zai-business-token-cache.ts
new file mode 100644
index 00000000..555346c2
--- /dev/null
+++ b/apps/ccbuddy-cli/packages/adapters/src/auth/zai-business-token-cache.ts
@@ -0,0 +1,125 @@
+import { ProjectAccessTokenTransientError } from "@ccbuddy/shared";
+
+const REFRESH_LEAD_MS = 120_000;
+const REFRESH_RETRY_BACKOFF_MS = 5_000;
+const UNKNOWN_TTL_MS = 300_000;
+const SECOND_MS = 1_000;
+
+export interface ZaiBusinessToken {
+ readonly token: string;
+ readonly refreshAt: number;
+ readonly expiresAt?: number;
+}
+interface Session {
+ oauth: string;
+ pending?: Promise;
+ value?: ZaiBusinessToken;
+ retryAt?: number;
+}
+
+/** 业务 JWT 与 PAT 生命周期不同;缓存只由 CLI 换证 adapter 持有。 */
+export class ZaiBusinessTokenCache {
+ #session?: Session;
+ readonly #rejected = new WeakSet();
+
+ constructor(private readonly onRefreshDeferred?: () => void) {}
+
+ clear(): void {
+ this.#session = undefined;
+ }
+
+ invalidate(value: ZaiBusinessToken): void {
+ // 迟到的旧 401 不得清除其它请求已经换好的业务 JWT。
+ this.#rejected.add(value);
+ if (this.#session?.value === value) this.#session.value = undefined;
+ }
+
+ wasRejected(value: ZaiBusinessToken): boolean {
+ return this.#rejected.has(value);
+ }
+
+ async resolve(oauth: string, exchange: () => Promise): Promise {
+ if (this.#session?.oauth !== oauth) this.#session = { oauth };
+ const session = this.#session;
+ if (session.pending) return session.pending;
+ if (
+ session.value &&
+ Date.now() <
+ Math.min(session.retryAt ?? session.value.refreshAt, session.value.expiresAt ?? Infinity)
+ )
+ return session.value;
+ const startedAt = Date.now();
+ const pending = Promise.resolve()
+ .then(() => {
+ if (this.#session !== session) throw new Error("project_token_scope_invalidated");
+ return exchange();
+ })
+ .then((raw) => {
+ if (this.#session !== session) throw new Error("project_token_scope_invalidated");
+ const response = raw as {
+ code?: unknown;
+ data?: {
+ access_token?: string;
+ accessToken?: string;
+ expires_in?: number;
+ expiresIn?: number;
+ };
+ };
+ if (response.code != null && ![0, 200, "0", "200"].includes(response.code as number))
+ throw new Error("project_token_login_failed");
+ const data = response.data;
+ const token = data?.access_token?.trim() || data?.accessToken?.trim();
+ if (!token) throw new Error("project_token_login_failed");
+ const ttl = data?.expires_in ?? data?.expiresIn;
+ const deadlines = [
+ readJwtExpiry(token),
+ typeof ttl === "number" && Number.isFinite(ttl) ? startedAt + ttl * SECOND_MS : undefined,
+ ].filter((value): value is number => value !== undefined);
+ const expiresAt = deadlines.length ? Math.min(...deadlines) : undefined;
+ const cacheUntil = expiresAt ?? startedAt + UNKNOWN_TTL_MS;
+ if (cacheUntil <= Date.now()) throw new Error("project_token_login_failed");
+ const value = Object.freeze({ token, expiresAt, refreshAt: cacheUntil - REFRESH_LEAD_MS });
+ session.value = value;
+ session.retryAt = undefined;
+ return value;
+ })
+ .catch((error: unknown) => {
+ if (this.#session !== session) throw new Error("project_token_scope_invalidated");
+ const value = session.value;
+ // 真实有效期未知时不能降级;保留原对象身份,让迟到的 401 仍能拒绝这一代 JWT。
+ if (
+ error instanceof ProjectAccessTokenTransientError &&
+ value &&
+ !this.wasRejected(value) &&
+ value.expiresAt !== undefined &&
+ Date.now() < value.expiresAt
+ ) {
+ session.retryAt = Math.min(Date.now() + REFRESH_RETRY_BACKOFF_MS, value.expiresAt);
+ this.onRefreshDeferred?.();
+ return value;
+ }
+ session.value = undefined;
+ session.retryAt = undefined;
+ throw error;
+ })
+ .finally(() => {
+ // 降级与清理属于共享 pending,所有并发调用必须获得同一个结果。
+ if (session.pending === pending) session.pending = undefined;
+ });
+ session.pending = pending;
+ return pending;
+ }
+}
+
+function readJwtExpiry(token: string): number | undefined {
+ try {
+ const raw = JSON.parse(
+ Buffer.from(token.split(".")[1] ?? "", "base64url").toString("utf8"),
+ ) as { exp?: unknown };
+ return typeof raw.exp === "number" && Number.isFinite(raw.exp)
+ ? raw.exp * SECOND_MS
+ : undefined;
+ } catch {
+ return undefined;
+ }
+}
diff --git a/apps/ccbuddy-cli/packages/adapters/src/browser/page-command.ts b/apps/ccbuddy-cli/packages/adapters/src/browser/page-command.ts
index be5deca2..fd09c535 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/browser/page-command.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/browser/page-command.ts
@@ -72,7 +72,7 @@ async function pointFor(
throw new Error("Browser action requires a ref or x/y coordinates");
}
-async function readManagedPageState(page: Page): Promise {
+export async function readManagedPageState(page: Page): Promise {
const client = await page.context().newCDPSession(page);
try {
const history = (await client.send("Page.getNavigationHistory")) as {
diff --git a/apps/ccbuddy-cli/packages/adapters/src/browser/session.ts b/apps/ccbuddy-cli/packages/adapters/src/browser/session.ts
index 91396d93..ec0d8a20 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/browser/session.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/browser/session.ts
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
import type { BrowserContext, Dialog, Page, ViewportSize } from "playwright-core";
import type { BrowserTabSummary } from "@ccbuddy/contracts";
-interface ManagedCdpTab {
+export interface ManagedCdpTab {
id: string;
page: Page;
}
diff --git a/apps/ccbuddy-cli/packages/adapters/src/config/schema.ts b/apps/ccbuddy-cli/packages/adapters/src/config/schema.ts
index 141b4c6b..ce2a1ee1 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/config/schema.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/config/schema.ts
@@ -11,7 +11,7 @@ const modelStreamSchema = z.object({
});
const permissionSchema = z.object({
- mode: z.enum(["plan", "build", "edit", "yolo", "auto"]).optional(),
+ mode: z.enum(["plan", "build", "edit", "yolo", "guarded", "auto"]).optional(),
allowedTools: z.array(z.string()).optional(),
disallowedTools: z.array(z.string()).optional(),
autoApproveHighRisk: z.boolean().optional(),
@@ -165,7 +165,10 @@ const pluginsSchema = z.object({
dirs: z.array(z.string().min(1)).optional(),
enabledPlugins: z.record(z.string(), z.boolean()).optional(),
extraKnownMarketplaces: z
- .record(z.string().min(1), z.object({ source: pluginMarketplaceSourceSchema }).strict())
+ .record(
+ z.string().min(1),
+ z.object({ source: pluginMarketplaceSourceSchema }).strict(),
+ )
.optional(),
options: z.record(z.string(), z.record(z.string(), pluginOptionValueSchema)).optional(),
suppressedBuiltins: z.array(z.string().min(1)).optional(),
@@ -231,7 +234,7 @@ const modelAnomalyGuardSchema = z.object({
// 因此本副本按原样保留(本包 zod 构造),并保持与 shared 的校验语义等价;
// 运行时校验语义仍以 shared 为准(discovery/trust 装配入口都走 shared schema——
// 本 schema 只负责配置文件装载诊断)。若未来统一 zod 实例,应删除本副本改 re-export。
-const hookProcessSchema = z
+export const hookProcessSchema = z
.object({
type: z.literal("process"),
command: z.string().min(1),
@@ -243,7 +246,7 @@ const hookProcessSchema = z
.passthrough();
-const hookCommandSchema = z
+export const hookCommandSchema = z
.object({
type: z.literal("command"),
command: z.string().min(1),
@@ -256,14 +259,14 @@ const hookCommandSchema = z
})
.passthrough();
-const hookMatcherSchema = z
+export const hookMatcherSchema = z
.object({
matcher: z.string().min(1).optional(),
hooks: z.array(z.discriminatedUnion("type", [hookProcessSchema, hookCommandSchema])).min(1),
})
.strict();
-const hooksSchema = z
+export const hooksSchema = z
.object({
enabled: z.boolean().optional(),
timeoutMs: positiveNumberSchema.optional(),
@@ -323,7 +326,7 @@ export interface ConfigDiagnostic {
severity: ConfigDiagnosticSeverity;
}
-interface ParseConfigFileResult {
+export interface ParseConfigFileResult {
config: RuntimeConfigPatch;
diagnostics: ConfigDiagnostic[];
}
@@ -436,8 +439,12 @@ function normalizePluginConfig(
}
const suppressedBuiltins = plugins.suppressedBuiltins
? plugins.suppressedBuiltins.reduce((ids, id) => {
- const canonicalId = id === LEGACY_CUA_PLUGIN_ID ? CANONICAL_CUA_PLUGIN_ID : id;
- if (canonicalId === CANONICAL_CUA_PLUGIN_ID && ids.includes(CANONICAL_CUA_PLUGIN_ID)) {
+ const canonicalId =
+ id === LEGACY_CUA_PLUGIN_ID ? CANONICAL_CUA_PLUGIN_ID : id;
+ if (
+ canonicalId === CANONICAL_CUA_PLUGIN_ID &&
+ ids.includes(CANONICAL_CUA_PLUGIN_ID)
+ ) {
return ids;
}
ids.push(canonicalId);
@@ -520,6 +527,10 @@ function isPlainRecord(value: unknown): value is Record {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
+function readPositiveNumber(value: unknown): number | undefined {
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
+}
+
function parseSkillsRuntimeConfig(
skills: CCbuddyConfigFile["skills"],
): RuntimeConfigPatch["skills"] | undefined {
diff --git a/apps/ccbuddy-cli/packages/adapters/src/device/cli-device-mid.ts b/apps/ccbuddy-cli/packages/adapters/src/device/cli-device-mid.ts
index d7e38e8f..397c97f8 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/device/cli-device-mid.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/device/cli-device-mid.ts
@@ -18,7 +18,7 @@ interface TelemetryLockOwner {
pid: number;
}
-interface EnsureCliDeviceMidOptions {
+export interface EnsureCliDeviceMidOptions {
baseDir?: string;
createId?: () => string;
env?: Record;
diff --git a/apps/ccbuddy-cli/packages/adapters/src/device/process-probe.ts b/apps/ccbuddy-cli/packages/adapters/src/device/process-probe.ts
index c21399bb..9a2578b4 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/device/process-probe.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/device/process-probe.ts
@@ -55,7 +55,7 @@ export interface ProcessProbe {
readonly treeScope: ProcessTreeScope;
}
-interface CreateProcessProbeOptions {
+export interface CreateProcessProbeOptions {
execFile?: ProcessProbeExecFile;
/** 列出 `/proc` 下的条目,仅 Linux 使用 */
listProcDirectory?: () => Promise;
@@ -66,7 +66,7 @@ interface CreateProcessProbeOptions {
readProcFile?: (path: string) => Promise;
}
-const PROCESS_PROBE_MAX_CONSECUTIVE_FAILURES = 3;
+export const PROCESS_PROBE_MAX_CONSECUTIVE_FAILURES = 3;
const PROBE_TIMED_OUT = Symbol("process-probe-timed-out");
diff --git a/apps/ccbuddy-cli/packages/adapters/src/exec/bash-file-output.ts b/apps/ccbuddy-cli/packages/adapters/src/exec/bash-file-output.ts
index 4d3375db..fe5ba0ba 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/exec/bash-file-output.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/exec/bash-file-output.ts
@@ -187,7 +187,8 @@ export async function diagnoseLostBashOutput(outputPath: string): Promise boolean;
@@ -25,7 +28,7 @@ export interface BashShellProvider {
shell: boolean | string;
}
-interface EffectiveBashShellResolution {
+export interface EffectiveBashShellResolution {
selection: ExecutionShellSelection;
provider?: BashShellProvider;
}
@@ -43,7 +46,7 @@ export function resolveEffectiveBashShellSelection(
: resolveEffectivePosixBashShellSelection(options);
}
-function resolvePosixBashShell(
+export function resolvePosixBashShell(
env: NodeJS.ProcessEnv,
exists?: ExecutableCheck,
): string | undefined {
@@ -71,7 +74,20 @@ function resolvePosixBashShell(
return undefined;
}
-function resolveWindowsGitBashShell(
+export function resolveWindowsBashShellProvider(options: {
+ env: NodeJS.ProcessEnv;
+ exists?: ExecutableCheck;
+ override?: ExecutionShellSelection;
+}): BashShellProvider | undefined {
+ return resolveEffectiveWindowsBashShellSelection({
+ env: options.env,
+ exists: options.exists,
+ override: options.override,
+ platform: "win32",
+ }).provider;
+}
+
+export function resolveWindowsGitBashShell(
env: NodeJS.ProcessEnv,
exists?: ExecutableCheck,
): string | undefined {
@@ -277,7 +293,9 @@ function resolveWindowsCmdOverridePath(
function isWindowsCmdFallback(shellPath: string): boolean {
return (
- !shellPath.includes("\\") && !shellPath.includes("/") && shellPath.toLowerCase() === "cmd.exe"
+ !shellPath.includes("\\") &&
+ !shellPath.includes("/") &&
+ shellPath.toLowerCase() === "cmd.exe"
);
}
diff --git a/apps/ccbuddy-cli/packages/adapters/src/exec/bash-startup-script.ts b/apps/ccbuddy-cli/packages/adapters/src/exec/bash-startup-script.ts
index 5e613758..775fde4d 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/exec/bash-startup-script.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/exec/bash-startup-script.ts
@@ -7,29 +7,29 @@ import { buildEmbeddedSearchPreludeContent } from "./embedded-search-prelude.js"
export type StartupShellDialect = ExecutionShellDialect | "legacy-shell";
-interface BashInternalScriptMaterializeOptions {
+export interface BashInternalScriptMaterializeOptions {
rootDir: string;
sessionId: string;
shellDialect?: StartupShellDialect;
}
-interface BashInternalScriptContent {
+export interface BashInternalScriptContent {
id: string;
content: string;
}
-interface MaterializedBashSourceScript {
+export interface MaterializedBashSourceScript {
path: string;
shellPath: string;
}
-interface BashSourceScript {
+export interface BashSourceScript {
path: string;
shellPath: string;
optional?: boolean;
}
-interface ApplyBashSourcesOptions {
+export interface ApplyBashSourcesOptions {
leadingSources?: BashSourceScript[];
rootDir: string;
sessionId: string;
@@ -40,7 +40,10 @@ export function applyBashSourcesToExecutionRequest(
request: ExecutionRequest,
options: ApplyBashSourcesOptions,
): ExecutionRequest {
- if (request.command.mode !== "shell" || request.command.shellProfile !== "posix-bash") {
+ if (
+ request.command.mode !== "shell" ||
+ request.command.shellProfile !== "posix-bash"
+ ) {
return request;
}
@@ -58,7 +61,10 @@ export function applyBashSourcesToExecutionRequest(
sessionId: options.sessionId,
shellDialect: options.shellDialect,
});
- const sources = [...(options.leadingSources ?? []), ...(materialized ? [materialized] : [])];
+ const sources = [
+ ...(options.leadingSources ?? []),
+ ...(materialized ? [materialized] : []),
+ ];
const command = applyBashSourceScripts(request.command.command, sources);
if (command === request.command.command) return request;
@@ -71,7 +77,7 @@ export function applyBashSourcesToExecutionRequest(
};
}
-function materializeBashInternalSourceScript(
+export function materializeBashInternalSourceScript(
script: BashInternalScriptContent | undefined,
options: BashInternalScriptMaterializeOptions,
): MaterializedBashSourceScript | undefined {
@@ -101,7 +107,14 @@ function materializeBashInternalSourceScript(
};
}
-function applyBashSourceScripts(command: string, sources: BashSourceScript[]): string {
+export function applyBashSourceScript(
+ command: string,
+ materialized: MaterializedBashSourceScript | undefined,
+): string {
+ return applyBashSourceScripts(command, materialized ? [materialized] : []);
+}
+
+export function applyBashSourceScripts(command: string, sources: BashSourceScript[]): string {
const sourceLines = sources.map((source) => {
const sourceCommand = `. ${quoteSourcePath(source)}`;
return source.optional ? `${sourceCommand} 2>/dev/null || true` : sourceCommand;
diff --git a/apps/ccbuddy-cli/packages/adapters/src/exec/cwd-capture.ts b/apps/ccbuddy-cli/packages/adapters/src/exec/cwd-capture.ts
index 9f3d710e..51789be6 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/exec/cwd-capture.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/exec/cwd-capture.ts
@@ -9,7 +9,7 @@ import {
windowsPathToGitBashPath,
} from "@ccbuddy/contracts";
-interface CwdCapturePlan {
+export interface CwdCapturePlan {
command: ExecutionCommand;
cwdFilePath?: string;
}
@@ -89,7 +89,10 @@ export function readCapturedCwd(
}
}
-function normalizeCapturedCwdForHost(value: string, dialect: ExecutionShellDialect): string {
+export function normalizeCapturedCwdForHost(
+ value: string,
+ dialect: ExecutionShellDialect,
+): string {
return dialect === "git-bash" ? gitBashPathToWindowsPath(value) : value;
}
diff --git a/apps/ccbuddy-cli/packages/adapters/src/exec/embedded-search-prelude.ts b/apps/ccbuddy-cli/packages/adapters/src/exec/embedded-search-prelude.ts
index 751d0051..52c7a529 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/exec/embedded-search-prelude.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/exec/embedded-search-prelude.ts
@@ -31,6 +31,15 @@ const UGREP_DEFAULT_ARGS = [
"--exclude-dir=.sl",
] as const;
+export function applyEmbeddedSearchPrelude(
+ command: string,
+ prelude?: ExecutionEmbeddedSearchPrelude,
+ options: EmbeddedSearchPreludeOptions = {},
+): string {
+ const content = buildEmbeddedSearchPreludeContent(prelude, options);
+ return content ? [content, command].join("\n") : command;
+}
+
export function buildEmbeddedSearchPreludeContent(
prelude?: ExecutionEmbeddedSearchPrelude,
options: EmbeddedSearchPreludeOptions = {},
diff --git a/apps/ccbuddy-cli/packages/adapters/src/exec/shell-init-snapshot.ts b/apps/ccbuddy-cli/packages/adapters/src/exec/shell-init-snapshot.ts
index 2a603edd..99e3a30f 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/exec/shell-init-snapshot.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/exec/shell-init-snapshot.ts
@@ -14,7 +14,7 @@ const DAY_MS = 24 * 60 * 60 * 1000;
const SNAPSHOT_TIMEOUT_MS = 10_000;
const SNAPSHOT_MAX_BUFFER_BYTES = 1_048_576;
-const DEFAULT_SHELL_INIT_SNAPSHOT_RETENTION_DAYS = 30;
+export const DEFAULT_SHELL_INIT_SNAPSHOT_RETENTION_DAYS = 30;
type ShellInitSnapshotExecFile = (
file: string,
@@ -31,41 +31,41 @@ const defaultExecFile: ShellInitSnapshotExecFile = async (file, args, options) =
};
};
-type ShellInitSnapshotDialect = Extract;
-type ShellInitSnapshotShellKind = "bash" | "zsh" | "sh";
+export type ShellInitSnapshotDialect = Extract;
+export type ShellInitSnapshotShellKind = "bash" | "zsh" | "sh";
-interface ShellInitSnapshot {
+export interface ShellInitSnapshot {
path: string;
shellPath: string;
}
-interface ShellInitSnapshotCleanupResult {
+export interface ShellInitSnapshotCleanupResult {
deleted: number;
errors: number;
}
-interface CleanupStaleShellInitSnapshotsOptions {
+export interface CleanupStaleShellInitSnapshotsOptions {
now?: Date;
retentionDays?: number;
rootDir: string;
}
-interface ShellInitSnapshotRequest {
+export interface ShellInitSnapshotRequest {
env: NodeJS.ProcessEnv;
rootDir: string;
shellDialect: StartupShellDialect;
shellPath: string;
}
-interface ShellInitSnapshotManagerOptions {
+export interface ShellInitSnapshotManagerOptions {
execFile?: ShellInitSnapshotExecFile;
}
-function shellInitSnapshotsDir(rootDir: string): string {
+export function shellInitSnapshotsDir(rootDir: string): string {
return join(rootDir, "shell-snapshots");
}
-class ShellInitSnapshotCleanupRegistry {
+export class ShellInitSnapshotCleanupRegistry {
private readonly paths = new Set();
register(snapshotPath: string): void {
@@ -132,7 +132,9 @@ export class ShellInitSnapshotManager {
const snapshotPath = join(snapshotsDir, createSnapshotFileName(shellKind));
const snapshotShellPath =
- request.shellDialect === "git-bash" ? windowsPathToGitBashPath(snapshotPath) : snapshotPath;
+ request.shellDialect === "git-bash"
+ ? windowsPathToGitBashPath(snapshotPath)
+ : snapshotPath;
const configPath = detectShellInitConfigPath({
env: request.env,
shellPath: request.shellPath,
@@ -225,7 +227,12 @@ export async function cleanupStaleShellInitSnapshots(
}
function isMissingFileError(error: unknown): boolean {
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
+ return (
+ typeof error === "object" &&
+ error !== null &&
+ "code" in error &&
+ error.code === "ENOENT"
+ );
}
export function supportsShellInitSnapshot(
@@ -234,14 +241,17 @@ export function supportsShellInitSnapshot(
return shellDialect === "posix" || shellDialect === "git-bash";
}
-function detectShellKind(shellPath: string): ShellInitSnapshotShellKind {
+export function detectShellKind(shellPath: string): ShellInitSnapshotShellKind {
const name = basename(shellPath).toLowerCase();
if (name.includes("zsh")) return "zsh";
if (name.includes("bash")) return "bash";
return "sh";
}
-function detectShellInitConfigPath(input: { env: NodeJS.ProcessEnv; shellPath: string }): string {
+export function detectShellInitConfigPath(input: {
+ env: NodeJS.ProcessEnv;
+ shellPath: string;
+}): string {
const home = input.env.HOME || input.env.USERPROFILE || "";
const shellKind = detectShellKind(input.shellPath);
if (shellKind === "zsh") return join(home, ".zshrc");
@@ -249,7 +259,7 @@ function detectShellInitConfigPath(input: { env: NodeJS.ProcessEnv; shellPath: s
return join(home, ".profile");
}
-function buildShellInitSnapshotCreationScript(input: {
+export function buildShellInitSnapshotCreationScript(input: {
configExists: boolean;
configPath: string;
pathValue?: string;
@@ -273,7 +283,7 @@ function buildShellInitSnapshotCreationScript(input: {
'echo "# Snapshot file" >| "$SNAPSHOT_FILE"',
"",
"# When this file is sourced, we first unalias to avoid conflicts",
- '# This is necessary because aliases get "frozen" inside function definitions at definition time,',
+ "# This is necessary because aliases get \"frozen\" inside function definitions at definition time,",
"# which can cause unexpected behavior when functions use commands that conflict with aliases",
'echo "# Unset all aliases to avoid conflicts with functions" >> "$SNAPSHOT_FILE"',
'echo "unalias -a 2>/dev/null || true" >> "$SNAPSHOT_FILE"',
@@ -305,7 +315,7 @@ export async function revalidateShellInitSnapshotForExecution(
function snapshotExportLines(shellKind: ShellInitSnapshotShellKind): string[] {
const aliasLines = [
'echo "# Aliases" >> "$SNAPSHOT_FILE"',
- '# Filter out winpty aliases on Windows to avoid "stdin is not a tty" errors',
+ "# Filter out winpty aliases on Windows to avoid \"stdin is not a tty\" errors",
"# Git Bash automatically creates aliases like \"alias node='winpty node.exe'\" for",
"# programs that need Win32 Console in mintty, but winpty fails when there's no TTY",
'if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then',
@@ -346,12 +356,12 @@ function snapshotExportLines(shellKind: ShellInitSnapshotShellKind): string[] {
" # Encode the function to base64, preserving all special characters",
' encoded_func=$(declare -f "$func" | base64 )',
" # Write the function definition to the snapshot",
- ' echo "eval \\"\\$(echo \'$encoded_func\' | base64 -d)\\" > /dev/null 2>&1" >> "$SNAPSHOT_FILE"',
+ " echo \"eval \\\"\\$(echo '$encoded_func' | base64 -d)\\\" > /dev/null 2>&1\" >> \"$SNAPSHOT_FILE\"",
"done",
"",
'echo "# Shell Options" >> "$SNAPSHOT_FILE"',
- 'shopt -p | head -n 1000 >> "$SNAPSHOT_FILE"',
- 'set -o | grep "on" | awk \'{print "set -o " $1}\' | head -n 1000 >> "$SNAPSHOT_FILE"',
+ "shopt -p | head -n 1000 >> \"$SNAPSHOT_FILE\"",
+ "set -o | grep \"on\" | awk '{print \"set -o \" $1}' | head -n 1000 >> \"$SNAPSHOT_FILE\"",
'echo "shopt -s expand_aliases" >> "$SNAPSHOT_FILE"',
...aliasLines,
];
diff --git a/apps/ccbuddy-cli/packages/adapters/src/http/index.ts b/apps/ccbuddy-cli/packages/adapters/src/http/index.ts
index a5fc74ed..7b516209 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/http/index.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/http/index.ts
@@ -4,7 +4,6 @@
import http from "node:http";
import https from "node:https";
-import { Readable } from "node:stream";
import { ProxyAgent } from "proxy-agent";
import {
createHttpClientError,
@@ -26,7 +25,8 @@ import {
defaultPublicDnsLookup,
type DnsLookup,
} from "./public-egress-policy.js";
-import { readResponseBody } from "./response-body.js";
+import { readIncomingResponse } from "../network/incoming-response.js";
+import { readResponseBody, type ResponseLike } from "./response-body.js";
const TRACE_HEADER = "x-ccbuddy-trace-id";
const DEFAULT_TIMEOUT_MS = 180_000;
@@ -163,7 +163,7 @@ function fetchHttpResponse(
proxyUrl: string | undefined,
tlsCaCertificates: Buffer | undefined,
publicDnsLookup: DnsLookup | undefined,
-): Promise {
+): Promise {
const useCustomTlsAgent = url.protocol === "https:" && tlsCaCertificates !== undefined;
if (!proxyUrl && !useCustomTlsAgent && !publicDnsLookup) {
return fetch(url.toString(), {
@@ -199,24 +199,14 @@ function fetchHttpResponse(
lookup: proxyUrl ? undefined : lookup,
},
(message) => {
- const responseHeaders = new Headers();
- for (const [name, value] of Object.entries(message.headers)) {
- if (Array.isArray(value)) {
- for (const item of value) {
- responseHeaders.append(name, item);
- }
- } else if (value !== undefined) {
- responseHeaders.append(name, String(value));
- }
+ // 不包成 `Response`:它表示不了 999 之类的状态码,而这里只要状态、头与正文(与原生 fetch 一样原样带回)。
+ // 回调里抛出的东西没人接得住、会打死整个 agent 进程,所以一律转成这一次请求的 reject。
+ try {
+ resolve(readIncomingResponse(message));
+ } catch (error) {
+ message.destroy();
+ reject(error);
}
-
- resolve(
- new Response(Readable.toWeb(message) as ReadableStream, {
- headers: responseHeaders,
- status: message.statusCode ?? 502,
- statusText: message.statusMessage,
- }),
- );
},
);
diff --git a/apps/ccbuddy-cli/packages/adapters/src/http/response-body.ts b/apps/ccbuddy-cli/packages/adapters/src/http/response-body.ts
index 15800f95..b2e31d03 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/http/response-body.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/http/response-body.ts
@@ -1,7 +1,17 @@
import { createHttpClientError } from "@ccbuddy/contracts";
+/**
+ * 读正文所需的最小形状:原生 fetch 的 `Response` 与 adapter 自己读出的 `IncomingResponse` 都满足它。
+ * 不要求是 `Response`:后者表示不了 200–599 之外的状态码(network/incoming-response.ts)。
+ */
+export interface ResponseLike {
+ status: number;
+ headers: Headers;
+ body: ReadableStream | null;
+}
+
export async function readResponseBody(
- response: Response,
+ response: ResponseLike,
maxResponseBytes: number,
signal: AbortSignal,
url: string,
@@ -28,18 +38,8 @@ export async function readResponseBody(
}
}
- if (!response.body) {
- const buffer = new Uint8Array(await response.arrayBuffer());
- if (buffer.byteLength > maxResponseBytes) {
- throw createHttpClientError({
- code: "too_large",
- url,
- status: response.status,
- message: `HTTP response is too large: bytes=${buffer.byteLength}, max=${maxResponseBytes}`,
- });
- }
- return buffer;
- }
+ // 没有正文流只发生在无正文状态(204 / 304 …):正文就是空的。
+ if (!response.body) return new Uint8Array(0);
const reader = response.body.getReader();
const chunks: Uint8Array[] = [];
diff --git a/apps/ccbuddy-cli/packages/adapters/src/index.ts b/apps/ccbuddy-cli/packages/adapters/src/index.ts
index 5c68b99e..18946989 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/index.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/index.ts
@@ -17,3 +17,4 @@ export * from "./auth/index.js";
export * from "./browser/index.js";
export * from "./mcp/index.js";
export * from "./workflow/index.js";
+export * from "./tools/index.js";
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/network.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/network.ts
index f5be5568..f4abf7ca 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/network.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/network.ts
@@ -1,6 +1,7 @@
import { accessSync, constants } from "node:fs";
import { delimiter, posix, win32 } from "node:path";
import { sanitizeCCbuddyRuntimeEnv } from "@ccbuddy/shared";
+import { withoutNetworkCapture } from "@ccbuddy/shared/node";
import { createNetworkProxyFetch } from "../network/proxy-fetch.js";
import { applyNetworkEgressEnv, type NetworkEgressEnvPolicy } from "../network/subprocess-env.js";
@@ -23,12 +24,13 @@ export function createMcpTransportFetch(options: {
env?: NodeJS.ProcessEnv;
network?: NetworkEgressEnvPolicy;
}): typeof globalThis.fetch {
- return createNetworkProxyFetch({
+ const fetch = createNetworkProxyFetch({
caCertFile: options.network?.caCertFile,
env: options.env ?? process.env,
httpProxy: options.network?.httpProxy,
noProxy: options.network?.noProxy,
});
+ return (input, init) => withoutNetworkCapture(() => fetch(input, init));
}
function filterStringEnv(env: NodeJS.ProcessEnv): Record {
@@ -51,7 +53,7 @@ const DEFAULT_RUNNING_NODE_PATH_OPTIONS: RunningNodePathOptions = {
platform: process.platform,
};
-function prependRunningNodeDirectory(
+export function prependRunningNodeDirectory(
env: Record,
options: RunningNodePathOptions = DEFAULT_RUNNING_NODE_PATH_OPTIONS,
): Record {
@@ -66,12 +68,20 @@ function prependRunningNodeDirectory(
const currentPath = env[pathKey] ?? "";
const nodeDirectory = pathApi.dirname(options.execPath);
const pathEntries = currentPath.split(pathDelimiter).filter(Boolean);
- if (pathEntries.some((entry) => arePathEntriesEqual(entry, nodeDirectory, options.platform))) {
+ if (
+ pathEntries.some((entry) =>
+ arePathEntriesEqual(entry, nodeDirectory, options.platform),
+ )
+ ) {
return env;
}
const nodeExecutableName = options.platform === "win32" ? "node.exe" : "node";
- if (pathEntries.some((entry) => options.isExecutable(pathApi.join(entry, nodeExecutableName)))) {
+ if (
+ pathEntries.some((entry) =>
+ options.isExecutable(pathApi.join(entry, nodeExecutableName)),
+ )
+ ) {
return env;
}
@@ -80,11 +90,17 @@ function prependRunningNodeDirectory(
// 目录可保持插件配置跨本地/SSH/WSL/Docker 可移植,同时不覆盖插件显式注入的环境。
return {
...env,
- [pathKey]: currentPath ? `${nodeDirectory}${pathDelimiter}${currentPath}` : nodeDirectory,
+ [pathKey]: currentPath
+ ? `${nodeDirectory}${pathDelimiter}${currentPath}`
+ : nodeDirectory,
};
}
-function arePathEntriesEqual(left: string, right: string, platform: NodeJS.Platform): boolean {
+function arePathEntriesEqual(
+ left: string,
+ right: string,
+ platform: NodeJS.Platform,
+): boolean {
const pathApi = platform === "win32" ? win32 : posix;
const normalize = (value: string) => {
const normalized = pathApi.normalize(value);
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-credentials.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-credentials.ts
index 5a2b2919..a888de12 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-credentials.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-credentials.ts
@@ -4,8 +4,8 @@ import type { OAuthClientInformationMixed, OAuthTokens } from "@modelcontextprot
import type { SharedCCbuddyCredentialStore } from "../auth/shared-credentials.js";
export const MCP_OAUTH_CANONICAL_CREDENTIALS_KEY = "authorization_credentials";
-const MCP_OAUTH_LEGACY_CLIENT_KEY = "client_information";
-const MCP_OAUTH_LEGACY_TOKENS_KEY = "tokens";
+export const MCP_OAUTH_LEGACY_CLIENT_KEY = "client_information";
+export const MCP_OAUTH_LEGACY_TOKENS_KEY = "tokens";
export const MCP_OAUTH_CREDENTIALS_VERSION = 2;
export const MCP_OAUTH_SUPPORTED_CREDENTIAL_VERSIONS = new Set([1, MCP_OAUTH_CREDENTIALS_VERSION]);
@@ -51,7 +51,7 @@ export function mcpOAuthCredentialKey(keyPrefix: string, name: string): string {
return `${keyPrefix}:${name}`;
}
-function createCredentialGeneration(): string {
+export function createCredentialGeneration(): string {
return randomBytes(16).toString("hex");
}
@@ -59,7 +59,10 @@ function createCredentialGeneration(): string {
* 迁移期 baseline:旧记录没有 `generation`,用 canonical 原始内容的稳定 hash 代替。
* 内容变化即 generation 变化,足以支撑 follower 的「是否换代」判断。
*/
-function resolveCredentialGeneration(canonical: McpOAuthCanonicalCredentials, raw: string): string {
+export function resolveCredentialGeneration(
+ canonical: McpOAuthCanonicalCredentials,
+ raw: string,
+): string {
if (typeof canonical.generation === "string" && canonical.generation.length > 0) {
return canonical.generation;
}
@@ -114,7 +117,7 @@ export async function loadCanonicalCredentials(
};
}
-interface PublishCanonicalCredentialsInput {
+export interface PublishCanonicalCredentialsInput {
clientInformation: OAuthClientInformationMixed;
issuer?: string;
obtainedAt?: number;
@@ -122,7 +125,7 @@ interface PublishCanonicalCredentialsInput {
tokens: OAuthTokens;
}
-interface PublishedCanonicalCredentials {
+export interface PublishedCanonicalCredentials {
canonical: McpOAuthCanonicalCredentials;
generation: string;
legacyClientRaw: string;
@@ -174,7 +177,7 @@ export function isRecord(value: unknown): value is Record {
}
/** access token 临期判定的安全余量。 */
-const MCP_OAUTH_EXPIRY_SKEW_MS = 30_000;
+export const MCP_OAUTH_EXPIRY_SKEW_MS = 30_000;
/**
* 是否需要在把 token 交给请求头之前先刷新。
@@ -191,7 +194,7 @@ export function isCanonicalTokenNearExpiry(
return now >= snapshot.expiresAt - skewMs;
}
-type CanonicalInvalidationScope = "tokens" | "all";
+export type CanonicalInvalidationScope = "tokens" | "all";
/**
* 按 canonical 快照做条件失效。
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-errors.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-errors.ts
index 852c3c26..c978b8c7 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-errors.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-errors.ts
@@ -1,8 +1,4 @@
-import {
- InsufficientScopeError,
- SdkErrorCode,
- UnauthorizedError,
-} from "@modelcontextprotocol/client";
+import { InsufficientScopeError, SdkErrorCode, UnauthorizedError } from "@modelcontextprotocol/client";
/**
* 交互授权需求的稳定标识。
@@ -15,8 +11,9 @@ import {
const INTERACTIVE_REQUIRED_BRAND = Symbol.for("ccbuddy.mcp.oauth.interactiveAuthorizationRequired");
const TEMPORARY_REFRESH_FAILURE_BRAND = Symbol.for("ccbuddy.mcp.oauth.temporaryRefreshFailure");
-const MCP_OAUTH_INTERACTIVE_REQUIRED_ERROR_CODE = "MCP_OAUTH_INTERACTIVE_REQUIRED";
-const MCP_OAUTH_TEMPORARY_REFRESH_FAILURE_ERROR_CODE = "MCP_OAUTH_TEMPORARY_REFRESH_FAILURE";
+export const MCP_OAUTH_INTERACTIVE_REQUIRED_ERROR_CODE = "MCP_OAUTH_INTERACTIVE_REQUIRED";
+export const MCP_OAUTH_TEMPORARY_REFRESH_FAILURE_ERROR_CODE =
+ "MCP_OAUTH_TEMPORARY_REFRESH_FAILURE";
export type McpOAuthInteractiveRequiredReason =
| "no_credentials"
@@ -27,14 +24,14 @@ export type McpOAuthInteractiveRequiredReason =
| "unauthorized"
| "legacy_provider_seam";
-interface McpOAuthInteractiveRequiredError extends Error {
+export interface McpOAuthInteractiveRequiredError extends Error {
code: typeof MCP_OAUTH_INTERACTIVE_REQUIRED_ERROR_CODE;
reason: McpOAuthInteractiveRequiredReason;
requiredScope?: string;
resourceMetadataUrl?: string;
}
-interface McpOAuthTemporaryRefreshFailureError extends Error {
+export interface McpOAuthTemporaryRefreshFailureError extends Error {
code: typeof MCP_OAUTH_TEMPORARY_REFRESH_FAILURE_ERROR_CODE;
}
@@ -82,13 +79,13 @@ export function createTemporaryRefreshFailureError(input: {
return error;
}
-function isInteractiveAuthorizationRequiredError(
+export function isInteractiveAuthorizationRequiredError(
error: unknown,
): error is McpOAuthInteractiveRequiredError {
return hasBrand(error, INTERACTIVE_REQUIRED_BRAND);
}
-function isTemporaryRefreshFailureError(
+export function isTemporaryRefreshFailureError(
error: unknown,
): error is McpOAuthTemporaryRefreshFailureError {
return hasBrand(error, TEMPORARY_REFRESH_FAILURE_BRAND);
@@ -149,7 +146,10 @@ function isSdkAuthenticationHttpError(error: unknown): boolean {
}
function hasBrand(error: unknown, brand: symbol): boolean {
- return (typeof error === "object" && error !== null) || typeof error === "function"
- ? (error as Record)[brand] === true
- : false;
+ return (
+ (typeof error === "object" && error !== null) ||
+ typeof error === "function"
+ ? (error as Record)[brand] === true
+ : false
+ );
}
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-interactive.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-interactive.ts
index c0372f41..f3354566 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-interactive.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-interactive.ts
@@ -47,7 +47,7 @@ export type McpInteractiveAuthorizationOutcome =
| { status: "pending"; authorizationUrl?: string }
| { status: "failed"; error: unknown };
-interface McpInteractiveAuthorizationInput {
+export interface McpInteractiveAuthorizationInput {
adapterInstanceId?: string;
config: McpAuthorizationCodeOAuthConfig;
credentialStore: SharedCCbuddyCredentialStore;
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-lease.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-lease.ts
index 5da904a8..0663f468 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-lease.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-lease.ts
@@ -5,7 +5,7 @@ import { CCBUDDY_FILE_LOCK_TIMEOUT_ERROR_CODE } from "@ccbuddy/shared";
import type { SharedCCbuddyCredentialStore } from "../auth/shared-credentials.js";
import { isRecord, mcpOAuthCredentialKey } from "./oauth-credentials.js";
-const MCP_OAUTH_PENDING_AUTHORIZATION_KEY = "pending_authorization";
+export const MCP_OAUTH_PENDING_AUTHORIZATION_KEY = "pending_authorization";
/**
* 授权 lease 的竞争等待预算。
@@ -19,7 +19,7 @@ const AUTHORIZATION_LEASE_MAX_WAIT_MS = 250;
const AUTHORIZATION_LEASE_RETRY_DELAYS_MS = [25] as const;
const AUTHORIZATION_LEASE_OWNERLESS_GRACE_MS = 100;
-interface McpOAuthAuthorizationLease {
+export interface McpOAuthAuthorizationLease {
attemptId: string;
release(): Promise;
}
@@ -30,7 +30,10 @@ interface McpOAuthAuthorizationLease {
* basename 只允许 hash 与连字符:credential key prefix 形如 `mcp:oauth:`,冒号在
* Windows 文件名中非法,直接拼进路径会让整个授权流程在 Windows 上失败。
*/
-function resolveAuthorizationLeasePath(credentialsFilePath: string, keyPrefix: string): string {
+export function resolveAuthorizationLeasePath(
+ credentialsFilePath: string,
+ keyPrefix: string,
+): string {
return join(dirname(credentialsFilePath), `${sanitizeKeyPrefix(keyPrefix)}.authz`);
}
@@ -68,7 +71,7 @@ export async function tryAcquireAuthorizationLease(input: {
}
}
-interface PendingAuthorizationRecord {
+export interface PendingAuthorizationRecord {
attemptId: string;
authorizationUrl: string;
baselineGeneration?: string;
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-provider.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-provider.ts
index aac87a47..c5704453 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-provider.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-provider.ts
@@ -7,7 +7,7 @@ import { refreshMcpOAuthTokensUnderLock } from "./oauth-refresh.js";
type McpAuthorizationCodeOAuthConfig = Extract;
-interface CreateMcpOAuthTokenProviderInput {
+export interface CreateMcpOAuthTokenProviderInput {
config: McpAuthorizationCodeOAuthConfig;
credentialStore: SharedCCbuddyCredentialStore;
fetchFn?: FetchLike;
@@ -31,7 +31,9 @@ interface CreateMcpOAuthTokenProviderInput {
* 绝不要把 `OAuthClientProvider` 传给运行期 transport:它会被 `adaptOAuthProvider` 包裹,
* 401 走 SDK 的 `handleOAuthUnauthorized()` → `auth()`,绕过 refresh 锁,并发刷新问题立即复发。
*/
-export function createMcpOAuthTokenProvider(input: CreateMcpOAuthTokenProviderInput): AuthProvider {
+export function createMcpOAuthTokenProvider(
+ input: CreateMcpOAuthTokenProviderInput,
+): AuthProvider {
const refreshInput = {
credentialStore: input.credentialStore,
...(input.fetchFn ? { fetchFn: input.fetchFn } : {}),
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-refresh.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-refresh.ts
index df5181f0..7412b831 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-refresh.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-refresh.ts
@@ -34,7 +34,7 @@ import { loadDiscoveryRecord, saveDiscoveryRecord } from "./oauth-shared.js";
*/
const REFRESH_LOCK_MAX_WAIT_MS = 45_000;
-interface RefreshMcpOAuthTokensInput {
+export interface RefreshMcpOAuthTokensInput {
credentialStore: SharedCCbuddyCredentialStore;
fetchFn?: FetchLike;
keyPrefix: string;
@@ -53,7 +53,7 @@ interface RefreshMcpOAuthTokensInput {
staticClientId?: string;
}
-interface ResolvedAsMetadata {
+export interface ResolvedAsMetadata {
authorizationServerUrl: string;
metadata?: AuthorizationServerMetadata;
resource?: URL;
@@ -74,13 +74,9 @@ export async function refreshMcpOAuthTokensUnderLock(
const lockPath = resolveRefreshLockPath(input.credentialStore.filePath, input.keyPrefix);
try {
- return await withFileLock(
- lockPath,
- async () => await refreshLocked(input, observedGeneration),
- {
- lockMaxWaitMs: REFRESH_LOCK_MAX_WAIT_MS,
- },
- );
+ return await withFileLock(lockPath, async () => await refreshLocked(input, observedGeneration), {
+ lockMaxWaitMs: REFRESH_LOCK_MAX_WAIT_MS,
+ });
} catch (error) {
if (!isCCbuddyFileLockTimeoutError(error)) throw error;
// 等锁超时不代表刷新失败:winner 可能已经发布结果。先重读,确认换代且有 token 就直接用。
@@ -245,7 +241,7 @@ function failSoft(
* discovery 记录带 TTL 且与 Phase 2 共用;`resource` 必须求出并带进 refresh 请求(RFC 8707),
* SDK 自己的 refresh 会传它,我们绕开 `auth()` 后必须自己补上,否则受众绑定丢失。
*/
-async function resolveAsMetadata(
+export async function resolveAsMetadata(
input: Pick<
RefreshMcpOAuthTokensInput,
"credentialStore" | "fetchFn" | "keyPrefix" | "serverUrl"
@@ -286,6 +282,6 @@ async function resolveAsMetadata(
* credentials.json 加锁;同一路径会自重入死锁。basename 只含 hash 与连字符(Windows 文件名
* 不允许冒号)。
*/
-function resolveRefreshLockPath(credentialsFilePath: string, keyPrefix: string): string {
+export function resolveRefreshLockPath(credentialsFilePath: string, keyPrefix: string): string {
return join(dirname(credentialsFilePath), `${sanitizeKeyPrefix(keyPrefix)}.refresh`);
}
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-shared.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-shared.ts
index b8a9b17a..c67d794f 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-shared.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth-shared.ts
@@ -2,11 +2,11 @@ import type { OAuthDiscoveryState } from "@modelcontextprotocol/client";
import type { SharedCCbuddyCredentialStore } from "../auth/shared-credentials.js";
import { isRecord, mcpOAuthCredentialKey } from "./oauth-credentials.js";
-const MCP_OAUTH_DISCOVERY_STATE_KEY = "discovery_state";
-const MCP_OAUTH_DISCOVERY_FETCHED_AT_KEY = "discovery_state_fetched_at";
+export const MCP_OAUTH_DISCOVERY_STATE_KEY = "discovery_state";
+export const MCP_OAUTH_DISCOVERY_FETCHED_AT_KEY = "discovery_state_fetched_at";
/** discovery metadata 缓存寿命。过期后重新发现,避免长期使用换过端点的旧 AS metadata。 */
-const MCP_OAUTH_DISCOVERY_TTL_MS = 24 * 60 * 60 * 1000;
+export const MCP_OAUTH_DISCOVERY_TTL_MS = 24 * 60 * 60 * 1000;
export interface McpOAuthAuthorizationContext {
authorizationUrl: string;
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth.ts
index c97b977d..171fd0a1 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/oauth.ts
@@ -1,7 +1,50 @@
-import { createHash } from "node:crypto";
-import type { McpOAuthConfig } from "@ccbuddy/contracts";
-import { type SharedCCbuddyCredentialStore } from "../auth/shared-credentials.js";
-import { type McpOAuthAuthorizationContext } from "./oauth-shared.js";
+import { createHash, randomBytes } from "node:crypto";
+import type {
+ OAuthClientInformationMixed,
+ OAuthClientMetadata,
+ OAuthClientProvider,
+ OAuthDiscoveryState,
+ OAuthTokens,
+} from "@modelcontextprotocol/client";
+import type { Logger, McpOAuthConfig } from "@ccbuddy/contracts";
+import {
+ createLocalhostOAuthCallbackServer,
+ type LocalhostOAuthCallbackServer,
+} from "../auth/localhost-callback.js";
+import {
+ createSharedCCbuddyCredentialStore,
+ type SharedCCbuddyCredentialStore,
+} from "../auth/shared-credentials.js";
+import {
+ deriveCredentialPair,
+ isCanonicalCredentials,
+ isRecord,
+ MCP_OAUTH_CANONICAL_CREDENTIALS_KEY,
+ MCP_OAUTH_SUPPORTED_CREDENTIAL_VERSIONS,
+ publishCanonicalCredentials,
+ type McpOAuthCanonicalCredentials,
+} from "./oauth-credentials.js";
+import { createInteractiveAuthorizationRequiredError } from "./oauth-errors.js";
+import {
+ loadDiscoveryRecord,
+ saveDiscoveryRecord,
+ type McpOAuthAuthorizationContext,
+} from "./oauth-shared.js";
+
+const DEFAULT_MCP_OAUTH_CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;
+
+interface CredentialSnapshot {
+ raw: string;
+ value: T;
+}
+
+interface StoredCredentialPair {
+ clientInformation?: OAuthClientInformationMixed;
+ source: "canonical" | "legacy";
+ tokens?: OAuthTokens;
+}
+
+type TransactionClientSource = "canonical" | "legacy" | "registered";
type McpAuthorizationCodeOAuthConfig = Extract;
export type { McpOAuthAuthorizationContext };
@@ -13,6 +56,505 @@ export interface McpOAuthRuntimeOptions {
openAuthorizationUrl?: (context: McpOAuthAuthorizationContext) => Promise | void;
}
+export interface McpOAuthSession {
+ close(): Promise;
+ readonly provider: OAuthClientProvider;
+ readonly timeoutMs: number;
+ hasPendingAuthorization(): boolean;
+ waitForAuthorizationCallback(): Promise;
+}
+
+export async function createMcpAuthorizationCodeOAuthSession(input: {
+ adapterInstanceId?: string;
+ config: McpAuthorizationCodeOAuthConfig;
+ logger?: Logger;
+ options?: McpOAuthRuntimeOptions;
+ serverName: string;
+ serverUrl: string;
+}): Promise {
+ const state = randomBytes(24).toString("base64url");
+ const callbackPath = normalizeCallbackPath(input.config.redirectPath, input.serverName);
+ const callbackServer = await createLocalhostOAuthCallbackServer({
+ callbackPath,
+ state,
+ });
+ const credentialStore = input.options?.credentialStore ?? createSharedCCbuddyCredentialStore();
+ const keyPrefix = createCredentialKeyPrefix(input.serverName, input.serverUrl, input.config);
+ const provider = new CCbuddyMcpAuthorizationCodeProvider({
+ adapterInstanceId: input.adapterInstanceId,
+ callbackServer,
+ config: input.config,
+ credentialStore,
+ keyPrefix,
+ logger: input.logger,
+ onAuthorizationRequired: input.options?.onAuthorizationRequired,
+ openAuthorizationUrl: input.options?.openAuthorizationUrl,
+ serverName: input.serverName,
+ state,
+ });
+
+ return {
+ close: async () => {
+ await callbackServer.close();
+ await provider.close();
+ },
+ provider,
+ timeoutMs: input.options?.authorizationTimeoutMs ?? DEFAULT_MCP_OAUTH_CALLBACK_TIMEOUT_MS,
+ hasPendingAuthorization: () => provider.hasPendingAuthorization(),
+ waitForAuthorizationCallback: async () => {
+ const callback = await callbackServer.waitForCallback();
+ return new URL(callback.url).searchParams;
+ },
+ };
+}
+
+class CCbuddyMcpAuthorizationCodeProvider implements OAuthClientProvider {
+ private readonly adapterInstanceId?: string;
+ private readonly callbackServer: LocalhostOAuthCallbackServer;
+ private readonly config: McpAuthorizationCodeOAuthConfig;
+ private readonly credentialStore: SharedCCbuddyCredentialStore;
+ private readonly keyPrefix: string;
+ private readonly logger?: Logger;
+ private readonly onAuthorizationRequired?: (
+ context: McpOAuthAuthorizationContext,
+ ) => Promise | void;
+ private readonly openAuthorizationUrl?: (
+ context: McpOAuthAuthorizationContext,
+ ) => Promise | void;
+ private readonly serverName: string;
+ private readonly stateValue: string;
+ private readonly transactionId: string;
+ private canonicalSnapshot?: CredentialSnapshot;
+ private credentialSnapshotsLoaded = false;
+ private legacyClientSnapshot?: CredentialSnapshot;
+ private legacyTokensSnapshot?: CredentialSnapshot;
+ private storedCredentialPair?: StoredCredentialPair;
+ private storedCredentialPairLoaded = false;
+ private pendingAuthorization = false;
+ private transactionClientInformation?: OAuthClientInformationMixed;
+ private transactionClientSource?: TransactionClientSource;
+ private verifierPersisted = false;
+
+ constructor(input: {
+ adapterInstanceId?: string;
+ callbackServer: LocalhostOAuthCallbackServer;
+ config: McpAuthorizationCodeOAuthConfig;
+ credentialStore: SharedCCbuddyCredentialStore;
+ keyPrefix: string;
+ logger?: Logger;
+ onAuthorizationRequired?: (context: McpOAuthAuthorizationContext) => Promise | void;
+ openAuthorizationUrl?: (context: McpOAuthAuthorizationContext) => Promise | void;
+ serverName: string;
+ state: string;
+ }) {
+ this.adapterInstanceId = input.adapterInstanceId;
+ this.callbackServer = input.callbackServer;
+ this.config = input.config;
+ this.credentialStore = input.credentialStore;
+ this.keyPrefix = input.keyPrefix;
+ this.logger = input.logger;
+ this.onAuthorizationRequired = input.onAuthorizationRequired;
+ this.openAuthorizationUrl = input.openAuthorizationUrl;
+ this.serverName = input.serverName;
+ this.stateValue = input.state;
+ this.transactionId = createHash("sha256").update(input.state).digest("hex");
+ }
+
+ get redirectUrl(): string {
+ return this.callbackServer.callbackUrl;
+ }
+
+ get clientMetadata(): OAuthClientMetadata {
+ return {
+ client_name: this.config.clientName ?? `CCbuddy ${this.serverName}`,
+ grant_types: ["authorization_code", "refresh_token"],
+ redirect_uris: [this.redirectUrl],
+ response_types: ["code"],
+ ...(this.config.clientSecret ? { token_endpoint_auth_method: "client_secret_basic" } : {}),
+ ...(this.config.scope ? { scope: this.config.scope } : {}),
+ };
+ }
+
+ state(): string {
+ return this.stateValue;
+ }
+
+ async clientInformation(): Promise {
+ if (this.config.clientId) {
+ return {
+ client_id: this.config.clientId,
+ ...(this.config.clientSecret ? { client_secret: this.config.clientSecret } : {}),
+ };
+ }
+ if (this.transactionClientInformation) {
+ return this.transactionClientInformation;
+ }
+
+ const storedPair = await this.loadStoredCredentialPair();
+ if (storedPair?.clientInformation) {
+ this.transactionClientInformation = storedPair.clientInformation;
+ this.transactionClientSource = storedPair.source;
+ return this.transactionClientInformation;
+ }
+
+ // 修复原因(根因 2 的过渡 seam):返回 undefined 会让 SDK 立刻做一次动态注册,而 DCR 的
+ // 归属已经整体移交 Phase 2。没有 client 也就没有 refresh token,本来就只能交互授权,
+ // 因此这里直接抛 interactiveRequired,避免在授权服务器上留下一个永不会被使用的注册。
+ throw createInteractiveAuthorizationRequiredError({
+ reason: "no_credentials",
+ serverName: this.serverName,
+ });
+ }
+
+ async saveClientInformation(clientInformation: OAuthClientInformationMixed): Promise {
+ // 修复原因:MCP SDK 在 authorization code exchange 时会再次读取 provider。
+ // 动态注册结果必须固定在当前 state 对应的 provider 实例;同时先写入 legacy client
+ // 作为 provider 重建后的恢复种子。当前事务后续只读内存值,不会被并发注册覆盖。
+ await this.credentialStore.save(
+ this.key("client_information"),
+ JSON.stringify(clientInformation),
+ );
+ this.transactionClientInformation = clientInformation;
+ this.transactionClientSource = "registered";
+ }
+
+ async tokens(): Promise {
+ return (await this.loadStoredCredentialPair())?.tokens;
+ }
+
+ async saveTokens(tokens: OAuthTokens): Promise {
+ const clientInformation = await this.clientInformation();
+ if (!clientInformation) {
+ throw new Error(`Missing MCP OAuth client information for ${this.serverName}`);
+ }
+ const previousTokens = this.storedCredentialPair?.tokens;
+ // 通过共享 helper 发布,确保 Phase 2 与本 provider 写出的 canonical 形态完全一致
+ // (含每次 publication 唯一的 generation)。client 与 refresh token 必须来自同一次授权,
+ // 三个 key 仍在同一个跨进程 read-modify-write 临界区内原子写入。
+ const published = await publishCanonicalCredentials(this.credentialStore, this.keyPrefix, {
+ clientInformation,
+ publishedBy: this.transactionId,
+ tokens,
+ });
+ this.canonicalSnapshot = { raw: published.raw, value: published.canonical };
+ this.legacyClientSnapshot = { raw: published.legacyClientRaw, value: clientInformation };
+ this.legacyTokensSnapshot = { raw: published.legacyTokensRaw, value: tokens };
+ this.credentialSnapshotsLoaded = true;
+ this.storedCredentialPair = {
+ clientInformation,
+ source: "legacy",
+ tokens,
+ };
+ this.storedCredentialPairLoaded = true;
+ this.transactionClientSource = "canonical";
+ // 修复原因:授权与刷新过去都没有成功侧事件,日志里只能看到 authorization.required,
+ // 无法判断一次 refresh 到底成功过没有。只记录安全标识与形态,不记录任何 token 明文。
+ this.logger?.info("MCP OAuth credentials published", {
+ event: "mcp.oauth.credentials.published",
+ ...this.logContext(),
+ clientIdHash: hashIdentifier(clientInformation.client_id),
+ grantKind: previousTokens ? "refresh" : "authorization_code",
+ hasRefreshToken: Boolean(tokens.refresh_token),
+ publishedGeneration: published.generation.slice(0, 12),
+ refreshTokenRotated: Boolean(
+ previousTokens?.refresh_token &&
+ tokens.refresh_token &&
+ previousTokens.refresh_token !== tokens.refresh_token,
+ ),
+ status: "completed",
+ tokenExpiresInSeconds: tokens.expires_in,
+ });
+ }
+
+ async redirectToAuthorization(authorizationUrl: URL): Promise {
+ this.pendingAuthorization = true;
+ // 修复原因(根因 2 的过渡 seam):本 provider 复用持久化的 DCR client,而它的 redirect_uris
+ // 锁死在注册当时的随机端口,`authorizationUrl` 因此可能带「旧 client_id + 新 redirect_uri」。
+ // 授权服务器按 RFC 6749 §4.1.2.1 禁止回跳、就地渲染错误页,回调永不到达且重试永不自愈。
+ // 所以这里不再投影该 URL、也不打开浏览器,而是抛出 interactiveRequired,由 adapter 编排层
+ // 转入 Phase 2 的 fresh DCR 授权事务(见 docs/mcp-oauth-two-phase.md §4.7)。
+ this.logger?.warn("MCP OAuth authorization handed off to interactive transaction", {
+ event: "mcp.oauth.authorization.handoff",
+ ...this.logContext(),
+ callbackPort: Number(new URL(this.redirectUrl).port),
+ status: "cancelled",
+ });
+ void authorizationUrl;
+ throw createInteractiveAuthorizationRequiredError({
+ reason: "legacy_provider_seam",
+ serverName: this.serverName,
+ });
+ }
+
+ async saveCodeVerifier(codeVerifier: string): Promise {
+ await this.credentialStore.save(this.verifierKey(), codeVerifier);
+ this.verifierPersisted = true;
+ }
+
+ async codeVerifier(): Promise {
+ const codeVerifier = await this.credentialStore.load(this.verifierKey());
+ if (!codeVerifier) {
+ throw new Error(`Missing MCP OAuth PKCE verifier for ${this.serverName}`);
+ }
+ this.verifierPersisted = true;
+ return codeVerifier;
+ }
+
+ async saveDiscoveryState(state: OAuthDiscoveryState): Promise {
+ // 与 Phase 2 共用同一份带时间戳的 discovery 记录:否则一侧刷新了 AS metadata,另一侧
+ // 仍可能使用无 TTL 的旧缓存。
+ await saveDiscoveryRecord(this.credentialStore, this.keyPrefix, state);
+ }
+
+ async discoveryState(): Promise {
+ return await loadDiscoveryRecord(this.credentialStore, this.keyPrefix);
+ }
+
+ async invalidateCredentials(scope: "all" | "client" | "tokens" | "verifier" | "discovery") {
+ // 修复原因:过去 refresh 失败只在 SDK 内部分类,CCbuddy 侧不留任何痕迹,事后无法区分
+ // token 过期、被撤销、client mismatch 和服务端临时错误(见 docs/analysis 的可观测性缺口)。
+ // SDK 不把 OAuth error code 传给 provider,但 invalidation scope 与之一一对应:
+ // tokens ← invalid_grant;client/all ← invalid_client / unauthorized_client。
+ this.logger?.warn("MCP OAuth credentials invalidated", {
+ event: "mcp.oauth.credentials.invalidated",
+ ...this.logContext(),
+ invalidationScope: scope,
+ status: "failed",
+ });
+ if (scope === "all" || scope === "client" || scope === "tokens") {
+ await this.invalidateCredentialSnapshot(scope);
+ }
+ if (scope === "all" || scope === "verifier") {
+ await this.clearVerifier();
+ }
+ if (scope === "all" || scope === "discovery") {
+ await this.credentialStore.delete(this.key("discovery_state"));
+ }
+ }
+
+ hasPendingAuthorization(): boolean {
+ return this.pendingAuthorization;
+ }
+
+ async close(): Promise {
+ // 修复原因:PKCE verifier 只属于当前 state。adapter 超时或关闭时必须清理本事务,
+ // 不能再删除相同 MCP 配置下另一个并发授权正在等待的 verifier。清理属于 best-effort:
+ // token exchange 已成功时,锁超时或损坏文件不能把主链路反转成连接失败。
+ try {
+ await this.clearVerifier();
+ } catch (error) {
+ this.logger?.warn("MCP OAuth verifier cleanup failed", {
+ event: "mcp.oauth.verifier.cleanup_failed",
+ adapterInstanceId: this.adapterInstanceId,
+ error: error instanceof Error ? error.message : String(error),
+ mcpServerName: this.serverName,
+ oauthStateId: this.transactionId.slice(0, 16),
+ processId: process.pid,
+ status: "failed",
+ });
+ }
+ this.transactionClientInformation = undefined;
+ this.transactionClientSource = undefined;
+ this.resetCredentialSnapshots();
+ }
+
+ private async invalidateCredentialSnapshot(scope: "all" | "client" | "tokens"): Promise {
+ await this.loadCredentialSnapshots();
+ const canonicalSnapshot = this.canonicalSnapshot;
+ const expectedValues: Record = {};
+ if (canonicalSnapshot) {
+ expectedValues[this.key(MCP_OAUTH_CANONICAL_CREDENTIALS_KEY)] = canonicalSnapshot.raw;
+ }
+ if ((scope === "all" || scope === "client") && this.legacyClientSnapshot) {
+ expectedValues[this.key("client_information")] = this.legacyClientSnapshot.raw;
+ }
+ if (
+ (scope === "all" || scope === "client" || scope === "tokens") &&
+ this.legacyTokensSnapshot
+ ) {
+ expectedValues[this.key("tokens")] = this.legacyTokensSnapshot.raw;
+ }
+ const deleted = await this.credentialStore.deleteIfValues(expectedValues);
+ const canonicalDeleted = canonicalSnapshot
+ ? deleted[this.key(MCP_OAUTH_CANONICAL_CREDENTIALS_KEY)]
+ : undefined;
+ this.logger?.warn("MCP OAuth credential snapshot invalidation resolved", {
+ event: "mcp.oauth.credentials.invalidate_snapshot",
+ ...this.logContext(),
+ canonicalDeleted,
+ // canonicalDeleted=false 表示 compare-and-delete 失败:另一个事务已发布新 pair,
+ // 本次失效必须保留 winner。这是并发收口的正常结果,不是错误。
+ hadCanonicalSnapshot: Boolean(canonicalSnapshot),
+ invalidationScope: scope,
+ legacyClientDeleted: deleted[this.key("client_information")],
+ legacyTokensDeleted: deleted[this.key("tokens")],
+ status: canonicalDeleted === false ? "cancelled" : "completed",
+ });
+
+ if (scope === "all" || scope === "client") {
+ this.transactionClientInformation = undefined;
+ this.transactionClientSource = undefined;
+ } else if (
+ this.transactionClientSource === "canonical" ||
+ (canonicalSnapshot && this.transactionClientSource === "legacy")
+ ) {
+ if (canonicalDeleted === false) {
+ // 修复原因:当前快照已被其他进程替换时,失败方不能删除新 pair;清空本地缓存,
+ // 让 SDK 的同一次重试重新读取获胜事务发布的 client/tokens。
+ this.transactionClientInformation = undefined;
+ this.transactionClientSource = undefined;
+ } else if (canonicalDeleted === true) {
+ // token 已失效但动态 client 仍可用于当前 provider 重新授权。
+ this.transactionClientSource = "registered";
+ }
+ }
+
+ this.resetCredentialSnapshots();
+ }
+
+ private async loadCredentialSnapshots(): Promise {
+ if (this.credentialSnapshotsLoaded) return;
+ const canonicalKey = this.key(MCP_OAUTH_CANONICAL_CREDENTIALS_KEY);
+ const legacyClientKey = this.key("client_information");
+ const legacyTokensKey = this.key("tokens");
+ const values = await this.credentialStore.loadMany([
+ canonicalKey,
+ legacyClientKey,
+ legacyTokensKey,
+ ]);
+ const parsedCanonicalSnapshot = await this.parseJsonSnapshot(
+ canonicalKey,
+ values[canonicalKey],
+ );
+ let canonicalSnapshot: CredentialSnapshot | undefined;
+ if (
+ parsedCanonicalSnapshot &&
+ isRecord(parsedCanonicalSnapshot.value) &&
+ typeof parsedCanonicalSnapshot.value.version === "number" &&
+ !MCP_OAUTH_SUPPORTED_CREDENTIAL_VERSIONS.has(parsedCanonicalSnapshot.value.version)
+ ) {
+ // 修复原因:CLI 与 desktop 会独立升级但共享凭据文件。旧版本只能忽略未知版本,
+ // 不能把新版本写入的 canonical pair 当损坏数据删除。
+ } else if (parsedCanonicalSnapshot && !isCanonicalCredentials(parsedCanonicalSnapshot.value)) {
+ await this.credentialStore.deleteIfValue(canonicalKey, parsedCanonicalSnapshot.raw);
+ } else {
+ canonicalSnapshot = parsedCanonicalSnapshot;
+ }
+ const legacyClientSnapshot = await this.parseJsonSnapshot(
+ legacyClientKey,
+ values[legacyClientKey],
+ );
+ const legacyTokensSnapshot = await this.parseJsonSnapshot(
+ legacyTokensKey,
+ values[legacyTokensKey],
+ );
+
+ // 修复原因:共享凭据读取可能因锁超时或临时 I/O 失败而抛错。只有完整快照成功后
+ // 才发布缓存和 loaded 标志,确保同一个 provider 的下一次调用仍会重试。
+ this.canonicalSnapshot = canonicalSnapshot;
+ this.legacyClientSnapshot = legacyClientSnapshot;
+ this.legacyTokensSnapshot = legacyTokensSnapshot;
+ this.credentialSnapshotsLoaded = true;
+ }
+
+ private async parseJsonSnapshot(
+ key: string,
+ raw: string | null | undefined,
+ ): Promise | undefined> {
+ if (!raw) return undefined;
+ try {
+ return { raw, value: JSON.parse(raw) as T };
+ } catch {
+ await this.credentialStore.deleteIfValue(key, raw);
+ return undefined;
+ }
+ }
+
+ private async loadStoredCredentialPair(): Promise {
+ if (this.storedCredentialPairLoaded) return this.storedCredentialPair;
+ await this.loadCredentialSnapshots();
+ // 委托给共享的纯函数:canonical 与 legacy 镜像的交错兼容规则只能有一份,两处各写一遍必然
+ // 发散。用已经读到的快照派生,不再多读一次凭据文件;快照本身仍留在本类里,因为
+ // compare-and-delete 需要发起方读到的原始值。
+ const pair = deriveCredentialPair({
+ canonicalRaw: this.canonicalSnapshot?.raw,
+ legacyClientRaw: this.legacyClientSnapshot?.raw,
+ legacyTokensRaw: this.legacyTokensSnapshot?.raw,
+ });
+ this.storedCredentialPair = pair
+ ? {
+ ...(pair.clientInformation ? { clientInformation: pair.clientInformation } : {}),
+ source: pair.source,
+ ...(pair.tokens ? { tokens: pair.tokens } : {}),
+ }
+ : undefined;
+ this.storedCredentialPairLoaded = true;
+ return this.storedCredentialPair;
+ }
+
+
+ private resetCredentialSnapshots(): void {
+ this.canonicalSnapshot = undefined;
+ this.credentialSnapshotsLoaded = false;
+ this.legacyClientSnapshot = undefined;
+ this.legacyTokensSnapshot = undefined;
+ this.storedCredentialPair = undefined;
+ this.storedCredentialPairLoaded = false;
+ }
+
+ private async loadJson(name: string): Promise {
+ const raw = await this.credentialStore.load(this.key(name));
+ if (!raw) return undefined;
+ try {
+ return JSON.parse(raw) as T;
+ } catch {
+ await this.credentialStore.deleteIfValue(this.key(name), raw);
+ return undefined;
+ }
+ }
+
+ private async saveJson(name: string, value: unknown): Promise {
+ await this.credentialStore.save(this.key(name), JSON.stringify(value));
+ }
+
+ private key(name: string): string {
+ return `${this.keyPrefix}:${name}`;
+ }
+
+ /** 并发诊断日志只允许安全标识:PID、adapter id、credential key prefix、state 短哈希。 */
+ private logContext(): Record {
+ return {
+ adapterInstanceId: this.adapterInstanceId,
+ credentialKeyPrefix: this.keyPrefix,
+ mcpServerName: this.serverName,
+ oauthStateId: this.transactionId.slice(0, 16),
+ processId: process.pid,
+ };
+ }
+
+ private verifierKey(): string {
+ return this.key(`code_verifier:${this.stateValue}`);
+ }
+
+ private async clearVerifier(): Promise {
+ if (!this.verifierPersisted) return;
+ await this.credentialStore.delete(this.verifierKey());
+ this.verifierPersisted = false;
+ }
+}
+
+/** 日志用脱敏摘要:client_id 等标识只记录哈希前缀,不记录原值。 */
+function hashIdentifier(value: string | undefined): string | undefined {
+ if (!value) return undefined;
+ return createHash("sha256").update(value).digest("hex").slice(0, 12);
+}
+
+function normalizeCallbackPath(value: string | undefined, serverName: string): string {
+ const fallback = `/oauth/callback/mcp/${encodeURIComponent(serverName)}`;
+ if (!value) return fallback;
+ return value.startsWith("/") ? value : `/${value}`;
+}
+
export function createCredentialKeyPrefix(
serverName: string,
serverUrl: string,
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/official-auth.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/official-auth.ts
index 2e165b51..3a8ba73d 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/official-auth.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/official-auth.ts
@@ -41,7 +41,7 @@ export class OfficialMcpAuthError extends Error {
const REQUEST_ID_HEADER = "x-request-id";
const TRACE_ID_HEADER = "x-trace-id";
-interface CreateOfficialMcpAuthFetchInput {
+export interface CreateOfficialMcpAuthFetchInput {
authHeadersPort?: OfficialMcpAuthHeadersPort;
baseFetch: typeof globalThis.fetch;
logger?: Logger;
@@ -407,7 +407,7 @@ async function readBoundedResponseText(
* ——parse 期已拦截静态保留头,这里是合并点的二次强制。
* 不触碰 mcp-session-id / mcp-protocol-version / accept / content-type。
*/
-function mergeOfficialAuthHeaders(
+export function mergeOfficialAuthHeaders(
incoming: HeadersInit | undefined,
authHeaders: Record,
): Headers {
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/pool.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/pool.ts
index 2f240168..463e905a 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/pool.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/pool.ts
@@ -17,7 +17,7 @@ import type { McpTelemetryTracker } from "./telemetry.js";
const DEFAULT_IDLE_GRACE_MS = 30_000;
-interface CreateMcpAdapterForPoolInput {
+export interface CreateMcpAdapterForPoolInput {
connectionContext: McpConnectionContext;
config: McpServerConfig;
serverName: string;
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/process-tree.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/process-tree.ts
index 59766043..419eef8f 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/process-tree.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/process-tree.ts
@@ -22,7 +22,7 @@ type ExecFileFn = (
options: ExecFileOptionsWithStringEncoding,
) => Promise;
-interface McpStdioProcessTreeTerminatorOptions {
+export interface McpStdioProcessTreeTerminatorOptions {
execFile?: ExecFileFn;
kill?: KillFn;
now?: () => number;
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/stdio-transport.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/stdio-transport.ts
index 954bcd13..93c6639a 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/stdio-transport.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/stdio-transport.ts
@@ -15,9 +15,9 @@ type SdkStdioDispose = (this: StdioClientTransport) => Promise;
const sdkDispose = Object.getOwnPropertyDescriptor(StdioClientTransport.prototype, "_dispose")
?.value as SdkStdioDispose | undefined;
-type StdioRequestMetaProvider = () => Promise | undefined>;
+export type StdioRequestMetaProvider = () => Promise | undefined>;
-interface StdioProcessExitInfo {
+export interface StdioProcessExitInfo {
exitCode: number | null;
exitedAt: number;
signal: NodeJS.Signals | null;
@@ -32,8 +32,10 @@ type ProcessTreeStdioServerParameters = StdioServerParameters & {
requestMetaProvider?: StdioRequestMetaProvider;
};
-interface ProcessTreeStdioClientTransportOptions {
- windowsJobObjectFactory?: (pid: number) => Promise;
+export interface ProcessTreeStdioClientTransportOptions {
+ windowsJobObjectFactory?: (
+ pid: number,
+ ) => Promise;
}
/**
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/telemetry.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/telemetry.ts
index 9d944376..7b049f6a 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/telemetry.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/telemetry.ts
@@ -11,7 +11,7 @@ const BUILTIN_NODE_REPL_SERVER_NAME = "node_repl";
const MCP_ID_SAFE_CHARACTER_PATTERN = /^[A-Za-z0-9._~-]$/;
const PLUGIN_MCP_NAMESPACE_PREFIX = "plugin:";
-type McpTelemetryEvent = CCbuddyMcpTelemetryEvent;
+export type McpTelemetryEvent = CCbuddyMcpTelemetryEvent;
export type McpTelemetrySource = Extract<
CCbuddyMcpTelemetryEvent,
{ kind: "process_start" }
@@ -72,7 +72,7 @@ export interface McpTelemetryTracker {
stop(): void;
}
-interface CreateMcpTelemetryTrackerOptions {
+export interface CreateMcpTelemetryTrackerOptions {
arch?: CCbuddyMcpTelemetryEvent["arch"];
idSalt: string;
now?: () => number;
diff --git a/apps/ccbuddy-cli/packages/adapters/src/mcp/windows-job-object.ts b/apps/ccbuddy-cli/packages/adapters/src/mcp/windows-job-object.ts
index c8d5b7cc..3344a300 100644
--- a/apps/ccbuddy-cli/packages/adapters/src/mcp/windows-job-object.ts
+++ b/apps/ccbuddy-cli/packages/adapters/src/mcp/windows-job-object.ts
@@ -12,7 +12,7 @@ export interface WindowsJobObjectController {
close(): void;
}
-interface WindowsJobObjectApi {
+export interface WindowsJobObjectApi {
create(): WindowsJobHandle | undefined;
assign(job: WindowsJobHandle, pid: number): boolean;
terminate(job: WindowsJobHandle): void;
@@ -110,12 +110,12 @@ async function createWindowsJobObjectApi(): Promise {
+ const previous = this.#auth;
+ const recover = this.#pending;
+ this.#pending = false;
+ this.#auth = undefined;
+ const token = previous && this.#token(previous);
+ const result = await refresh({
+ ...input,
+ ...(recover && token && previous?.accountScope
+ ? {
+ expectedAccountScope: previous.accountScope,
+ rejectedProjectTokenFingerprint: await projectAccessTokenFingerprint(token),
+ }
+ : {}),
+ });
+ if (recover && result.requestAuth?.accountScope !== previous?.accountScope)
+ throw new Error("project_token_scope_invalidated");
+ this.#auth = result.headersApplied ? result.requestAuth : undefined;
+ return result;
+ },
+ };
+ }
+
+ claim(error: unknown, blocked = false): boolean {
+ if (this.#verification.claim(error, blocked)) return true;
+ if (!this.canRecoverProjectToken(error, blocked)) return false;
+ this.#used = true;
+ this.#pending = true;
+ this.logger?.warn("Model PAT rejected; refreshing once before retry", {
+ event: "model.project_token.refresh_retry",
+ providerId: String(this.model.providerId),
+ modelId: String(this.model.modelId),
+ traceId: this.request.traceContext?.traceId,
+ statusCode: 401,
+ });
+ return true;
+ }
+
+ canRecoverProjectToken(error: unknown, blocked = false): boolean {
+ const mode = this.model.accountAccess?.mode;
+ if (
+ blocked ||
+ this.#used ||
+ this.request.abortSignal?.aborted ||
+ !this.request.refreshRuntimeHeadersBeforeAttempt ||
+ !["individual-coding-plan", "team-coding-plan", "off-peak"].includes(mode ?? "") ||
+ !this.#auth?.accountScope ||
+ !this.#token(this.#auth) ||
+ !isProjectTokenRejection(error)
+ )
+ return false;
+ return true;
+ }
+
+ #token(auth: ModelRequestAuth): string | undefined {
+ return this.model.accountAccess?.mode === "off-peak"
+ ? Object.entries(auth.headers ?? {}).find(
+ ([key]) => key.toLowerCase() === "x-coding-plan-api-key",
+ )?.[1]
+ : auth.apiKey;
+ }
+}
+
+export function isProjectTokenRejection(error: unknown): boolean {
+ return (
+ getHttpResponseStatus(unwrapRetryError(error)) === 401 &&
+ !isOtherAuthRejection(error, new WeakSet