Add profile-scoped tool access and plan goal mode - #3805
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR introduces profile-scoped settings (including tool access policies) and adds a new “plan and goal” interaction mode that can optionally auto-start execution after a plan is accepted. It also extends provider contracts to surface skill tool dependencies and to propagate an optional goal objective into provider turn execution.
Changes:
- Add profiles + profile/project-scoped tool access policies, with UI controls in Settings and in project context menus.
- Add
plan_and_goalinteraction mode (plus/plan-goalslash command) and optionally auto-start plan execution based on a new client setting. - Extend provider/contracts to support skill
toolDependenciesand an optionalgoalObjectivepassed through orchestration into provider runtimes.
Reviewed changes
Copilot reviewed 45 out of 46 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-workspace.yaml | Adds dependency overrides (undici, xcode>uuid). |
| pnpm-lock.yaml | Updates lockfile to reflect new overrides/versions. |
| packages/shared/src/composerTrigger.ts | Adds /plan-goal parsing support in shared trigger detection. |
| packages/contracts/src/settings.ts | Adds profile + tool-policy + plan-and-goal review mode settings to contracts. |
| packages/contracts/src/server.ts | Extends skill schema with optional toolDependencies. |
| packages/contracts/src/server.test.ts | Adds decoding coverage for skill toolDependencies. |
| packages/contracts/src/provider.ts | Adds optional goalObjective to send-turn input schema. |
| packages/contracts/src/orchestration.ts | Adds plan_and_goal interaction mode + propagates goalObjective in turn-start schemas. |
| packages/client-runtime/src/relay/discovery.ts | Adjusts relay discovery failure handling. |
| apps/web/src/toolAccess.ts | Adds tool catalog building + policy resolution/filtering helpers. |
| apps/web/src/toolAccess.test.ts | Adds tests for catalog building and filtering by policy. |
| apps/web/src/sidebarSubchats.ts | Derives sidebar “subchat” summaries from orchestration activities. |
| apps/web/src/sidebarSubchats.test.ts | Adds tests for subchat derivation/deduping behavior. |
| apps/web/src/proposedPlan.ts | Adds goal-objective helper + expands plan follow-up submission output. |
| apps/web/src/proposedPlan.test.ts | Adds tests for goal-objective + plan-and-goal follow-up behavior. |
| apps/web/src/profiles.ts | Implements profile normalization, assignment patch helpers, and filtering utilities. |
| apps/web/src/profiles.test.ts | Adds tests for profile filtering and assignment patch generation. |
| apps/web/src/index.css | Updates theme variables and adds grid/scanline styling + mono fonts in chrome. |
| apps/web/src/composerDraftStore.ts | Uses schema-based validation for persisted ProviderInteractionMode. |
| apps/web/src/composer-logic.ts | Adds /plan-goal parsing support in web composer logic. |
| apps/web/src/composer-logic.test.ts | Adds tests for /plan-goal parsing. |
| apps/web/src/components/Sidebar.tsx | Adds profile switcher, project tool customization dialog, subchat UI, and profile filtering. |
| apps/web/src/components/Sidebar.logic.ts | Treats plan_and_goal like plan for plan-ready prompt logic. |
| apps/web/src/components/settings/ToolAccessPolicyControl.tsx | Adds UI control for tool access policies by scope (global/profile/project). |
| apps/web/src/components/settings/SettingsPanels.tsx | Adds background color, plan+goal review mode, tool policy controls, and provider profile scoping. |
| apps/web/src/components/settings/ProviderInstanceCard.tsx | Adjusts layout for header actions (supports new scope control + reset button). |
| apps/web/src/components/CommandPalette.tsx | Filters projects/threads by active profile and auto-assigns new/existing projects to active profile. |
| apps/web/src/components/ChatView.tsx | Adds plan-and-goal mode behavior, goalObjective propagation, and optional auto-start for plan+goal. |
| apps/web/src/components/chat/CompactComposerControlsMenu.tsx | Allows selecting plan_and_goal in interaction mode menu. |
| apps/web/src/components/chat/ChatComposer.tsx | Filters providers/skills by active profile + tool access policy; adds /plan-goal. |
| apps/web/src/components/AppSidebarLayout.tsx | Applies user-configured background color via CSS var and syncs browser chrome theme. |
| apps/server/src/provider/Layers/OpenCodeAdapter.ts | Maps plan_and_goal to plan agent selection. |
| apps/server/src/provider/Layers/CursorAdapter.ts | Treats plan_and_goal like plan for mode resolution. |
| apps/server/src/provider/Layers/CodexSessionRuntime.ts | Maps plan_and_goal collaboration to Codex plan mode; sends goal objective to Codex thread. |
| apps/server/src/provider/Layers/CodexSessionRuntime.test.ts | Adds test for plan-and-goal mapping to Codex plan mode. |
| apps/server/src/provider/Layers/CodexProvider.ts | Parses and exposes skill tool dependency metadata. |
| apps/server/src/provider/Layers/CodexAdapter.ts | Passes goalObjective through to Codex session runtime sendTurn input. |
| apps/server/src/provider/Layers/ClaudeAdapter.ts | Treats plan_and_goal like plan for permission mode changes. |
| apps/server/src/orchestration/Layers/ProviderCommandReactor.ts | Propagates interactionMode type widening + goalObjective through to provider input. |
| apps/server/src/orchestration/decider.ts | Threads goalObjective into turn start command payload. |
| apps/mobile/src/features/threads/threadPresentation.ts | Treats plan_and_goal like plan for plan-ready status. |
| apps/mobile/src/features/threads/ThreadComposer.tsx | Adds /plan-goal and maps it to plan_and_goal. |
| apps/mobile/src/features/threads/NewTaskDraftScreen.tsx | Adds UI selection/labeling for plan_and_goal. |
| apps/desktop/src/wsl/DesktopWslEnvironment.ts | Refactors WSL user home lookup into an Effect.fn. |
| apps/desktop/src/settings/DesktopClientSettings.test.ts | Updates test client settings object with new required client settings keys. |
| apps/desktop/src/backend/DesktopBackendPool.test.ts | Simplifies an Effect test case using direct pipe/provide. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return; | ||
| } | ||
| return yield* Effect.fail(failure); | ||
| return yield* failure; | ||
| } |
| > | ||
| <Checkbox | ||
| checked={selectedKeys.has(entry.key)} | ||
| disabled={!entry.enabled} |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c1109a350a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const normalizedProjectPolicy = normalizeToolAccessPolicy(projectPolicy, "inherit"); | ||
| if (normalizedProjectPolicy.mode === "custom") { | ||
| return new Set(normalizedProjectPolicy.enabledToolKeys); | ||
| } |
There was a problem hiding this comment.
Stop inheriting when a scope selects all tools
When a project or profile policy is explicitly set to mode: "all", this resolver falls through to the parent scope and can still return a custom restriction. For example, with a global custom allow-list and the active profile set to “All tools”, the profile still inherits the global allow-list instead of allowing all tools, even though the UI presents “All tools” as distinct from “Use global”/“Use profile”. Return null as soon as the project/profile policy resolves to all.
Useful? React with 👍 / 👎.
| const selectedProviderSkills = useMemo( | ||
| () => | ||
| filterProviderSkillsForToolAccess({ | ||
| provider: selectedProviderStatus, | ||
| catalog: toolCatalog, | ||
| settings, | ||
| project: activeProject, | ||
| }), |
There was a problem hiding this comment.
This applies the access policy only to the composer’s filtered skill list, so the policy is bypassed whenever a user types or pastes a blocked $skill manually, or reuses an old draft containing one; the raw prompt is still sent to the provider and the provider session still has all installed skills. If these settings are meant to control which tools are available to a chat, the send/provider path needs to reject or strip disallowed skill/tool invocations rather than only hiding them from autocomplete.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c1109a3. Configure here.
| text: followUp.text, | ||
| interactionMode: followUp.interactionMode, | ||
| ...(followUp.goalObjective !== undefined ? { goalObjective: followUp.goalObjective } : {}), | ||
| }); |
There was a problem hiding this comment.
Auto goal skips retry
High Severity
The useEffect for auto-starting plan-and-goal execution adds an entry to autoStartedPlanAndGoalIdsRef before the onSubmitPlanFollowUp call completes. If the submission then fails or returns early, the plan is incorrectly marked as handled, preventing any further automatic execution for it in the current session.
Reviewed by Cursor Bugbot for commit c1109a3. Configure here.
| if (input.interactionMode === "plan" || input.interactionMode === "plan_and_goal") { | ||
| return findModeByAliases(modeState.availableModes, ACP_PLAN_MODE_ALIASES)?.id; | ||
| } |
There was a problem hiding this comment.
🟡 Medium Layers/CursorAdapter.ts:227
When interactionMode is "plan_and_goal" and the Cursor ACP runtime exposes only non-plan modes (e.g. ask/code), resolveRequestedModeId returns undefined because the "plan_and_goal" branch only tries ACP_PLAN_MODE_ALIASES. applyRequestedSessionConfiguration then skips setMode, so the turn silently runs in whatever mode was active before instead of a planning mode. The "plan"-only check should either fall through to the same fallbacks used by the approval-required and default branches, or "plan_and_goal" should be handled by those fallbacks as well.
- if (input.interactionMode === "plan" || input.interactionMode === "plan_and_goal") {
- return findModeByAliases(modeState.availableModes, ACP_PLAN_MODE_ALIASES)?.id;
- }
+ if (input.interactionMode === "plan" || input.interactionMode === "plan_and_goal") {
+ return (
+ findModeByAliases(modeState.availableModes, ACP_PLAN_MODE_ALIASES)?.id ??
+ modeState.availableModes.find((mode) => !isPlanMode(mode))?.id ??
+ modeState.currentModeId
+ );
+ }🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/CursorAdapter.ts around lines 227-229:
When `interactionMode` is `"plan_and_goal"` and the Cursor ACP runtime exposes only non-plan modes (e.g. `ask`/`code`), `resolveRequestedModeId` returns `undefined` because the `"plan_and_goal"` branch only tries `ACP_PLAN_MODE_ALIASES`. `applyRequestedSessionConfiguration` then skips `setMode`, so the turn silently runs in whatever mode was active before instead of a planning mode. The `"plan"`-only check should either fall through to the same fallbacks used by the `approval-required` and default branches, or `"plan_and_goal"` should be handled by those fallbacks as well.
| const normalized = normalizeToolAccessPolicy(policy, scope === "global" ? "all" : "inherit"); | ||
| const selectedKeys = new Set(normalized.enabledToolKeys); | ||
| const enabledCatalogKeys = catalog.filter((entry) => entry.enabled).map((entry) => entry.key); | ||
| const customPolicyWithCurrentCatalog = (): AppToolAccessPolicy => ({ |
There was a problem hiding this comment.
🟡 Medium settings/ToolAccessPolicyControl.tsx:46
When an inherited profile or project policy is switched to "custom" via the mode selector, customPolicyWithCurrentCatalog returns a policy with every enabled catalog tool selected, rather than preserving the tools the user was already effectively inheriting. The bug is that customPolicyWithCurrentCatalog checks normalized.enabledToolKeys.length > 0, but normalizeToolAccessPolicy leaves enabledToolKeys empty for an "inherit" policy, so the fallback at line 51 fires and replaces the inherited subset with [...new Set(enabledCatalogKeys)].sort(). A user who switches from inherited to custom to tweak one tool silently grants all tools instead of starting from the inherited selection. Consider seeding customPolicyWithCurrentCatalog from the effective inherited selection rather than from normalized.enabledToolKeys, or documenting the all-tools default if it is intentional.
Also found in 1 other location(s)
apps/web/src/components/settings/SettingsPanels.tsx:1092
GeneralSettingsPanelrendersToolAccessPolicyControlimmediately withcatalog={toolCatalog}, buttoolCatalogis built fromprimaryServerProvidersAtom, which is[]until the server config finishes loading. If the user switches the new Tools setting tocustomduring that window,ToolAccessPolicyControlcreates{ mode: "custom", enabledToolKeys: [] }.resolveEffectiveToolAccess()treats that as an explicit allow-list with no entries, so once providers arrive every tool is silently blocked until the user reopens settings and reselects tools.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/ToolAccessPolicyControl.tsx around line 46:
When an inherited profile or project policy is switched to `"custom"` via the mode selector, `customPolicyWithCurrentCatalog` returns a policy with **every enabled catalog tool** selected, rather than preserving the tools the user was already effectively inheriting. The bug is that `customPolicyWithCurrentCatalog` checks `normalized.enabledToolKeys.length > 0`, but `normalizeToolAccessPolicy` leaves `enabledToolKeys` empty for an `"inherit"` policy, so the fallback at line 51 fires and replaces the inherited subset with `[...new Set(enabledCatalogKeys)].sort()`. A user who switches from inherited to custom to tweak one tool silently grants all tools instead of starting from the inherited selection. Consider seeding `customPolicyWithCurrentCatalog` from the effective inherited selection rather than from `normalized.enabledToolKeys`, or documenting the all-tools default if it is intentional.
Also found in 1 other location(s):
- apps/web/src/components/settings/SettingsPanels.tsx:1092 -- `GeneralSettingsPanel` renders `ToolAccessPolicyControl` immediately with `catalog={toolCatalog}`, but `toolCatalog` is built from `primaryServerProvidersAtom`, which is `[]` until the server config finishes loading. If the user switches the new Tools setting to `custom` during that window, `ToolAccessPolicyControl` creates `{ mode: "custom", enabledToolKeys: [] }`. `resolveEffectiveToolAccess()` treats that as an explicit allow-list with no entries, so once providers arrive every tool is silently blocked until the user reopens settings and reselects tools.
There was a problem hiding this comment.
🟡 Medium
Selecting the /plan-goal command sets interactionMode to "plan_and_goal", but the Configuration menu only offers "default" and "plan" as choices. As a result, the subtitle always displays "Default" and no menu item is shown as active when plan_and_goal is selected, so the mode is misreported and can't be changed back from the toolbar. Consider adding a "plan_and_goal" option to the interaction menu and updating the subtitle logic to cover all three values.
Also found in 1 other location(s)
apps/mobile/src/features/threads/NewTaskDraftScreen.tsx:294
Adding the
options:interaction:plan_and_goalchoice here creates threads inplan_and_goalmode, but the mobile thread composer still only understandsdefaultandplan. After starting one of these tasks,ThreadComposercomputessubtitle: currentInteractionMode === "plan" ? "Plan" : "Default"and only offersdefault/plansubactions, so the current mode is shown asDefaultand cannot be preserved or re-selected from the thread UI. This new entry exposes a persistent, user-visible mismatch in the main composer controls for every task started withplan_and_goal.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/features/threads/ThreadComposer.tsx around line 644:
Selecting the `/plan-goal` command sets `interactionMode` to `"plan_and_goal"`, but the Configuration menu only offers `"default"` and `"plan"` as choices. As a result, the subtitle always displays `"Default"` and no menu item is shown as active when `plan_and_goal` is selected, so the mode is misreported and can't be changed back from the toolbar. Consider adding a `"plan_and_goal"` option to the interaction menu and updating the subtitle logic to cover all three values.
Also found in 1 other location(s):
- apps/mobile/src/features/threads/NewTaskDraftScreen.tsx:294 -- Adding the `options:interaction:plan_and_goal` choice here creates threads in `plan_and_goal` mode, but the mobile thread composer still only understands `default` and `plan`. After starting one of these tasks, `ThreadComposer` computes `subtitle: currentInteractionMode === "plan" ? "Plan" : "Default"` and only offers `default`/`plan` subactions, so the current mode is shown as `Default` and cannot be preserved or re-selected from the thread UI. This new entry exposes a persistent, user-visible mismatch in the main composer controls for every task started with `plan_and_goal`.
| sendTurn: (input) => | ||
| Effect.gen(function* () { | ||
| const providerThreadId = yield* readProviderThreadId; | ||
| if (input.goalObjective?.trim()) { |
There was a problem hiding this comment.
🟡 Medium Layers/CodexSessionRuntime.ts:1271
sendTurn calls thread/goal/set to update the objective, then proceeds to config/mcpServer/reload and turn/start. If any of those later steps fail, the caller receives an error, but the thread goal on the remote Codex thread has already been mutated and is never reverted. On retry, the next turn runs with a stale objective from a turn that was reported as failed. Consider setting the goal only after turn/start succeeds, or reverting the goal in a failure branch before the effect returns.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/CodexSessionRuntime.ts around line 1271:
`sendTurn` calls `thread/goal/set` to update the objective, then proceeds to `config/mcpServer/reload` and `turn/start`. If any of those later steps fail, the caller receives an error, but the thread goal on the remote Codex thread has already been mutated and is never reverted. On retry, the next turn runs with a stale objective from a turn that was reported as failed. Consider setting the goal only after `turn/start` succeeds, or reverting the goal in a failure branch before the effect returns.
| function assignedProfileIdForProject( | ||
| settings: Pick<ClientSettings, "projectProfileAssignments">, | ||
| projectKey: string, | ||
| ): string { | ||
| return settings.projectProfileAssignments?.[projectKey] ?? DEFAULT_PROFILE_ID; | ||
| } |
There was a problem hiding this comment.
🟡 Medium src/profiles.ts:122
assignedProfileIdForProject returns a stale profile id when a profile has been removed from settings.profiles (or normalized away because its id or name trims to empty). Any project or thread assigned to that orphaned id stops matching the active profile and disappears from the sidebar and command palette instead of falling back to the default profile. The function checks whether projectProfileAssignments has an entry for projectKey, but it never validates that the stored id is still present in getAppProfiles(settings). Consider resolving the stored id against the current profile set and returning DEFAULT_PROFILE_ID when it no longer exists.
| function assignedProfileIdForProject( | |
| settings: Pick<ClientSettings, "projectProfileAssignments">, | |
| projectKey: string, | |
| ): string { | |
| return settings.projectProfileAssignments?.[projectKey] ?? DEFAULT_PROFILE_ID; | |
| } | |
| function assignedProfileIdForProject( | |
| settings: Pick<ClientSettings, "profiles" | "projectProfileAssignments">, | |
| projectKey: string, | |
| ): string { | |
| const assignedProfileId = settings.projectProfileAssignments?.[projectKey]; | |
| if (!assignedProfileId) { | |
| return DEFAULT_PROFILE_ID; | |
| } | |
| return getAppProfiles(settings).some((profile) => profile.id === assignedProfileId) | |
| ? assignedProfileId | |
| : DEFAULT_PROFILE_ID; | |
| } |
Also found in 1 other location(s)
packages/contracts/src/settings.ts:121
projectProfileAssignmentsstores arbitraryTrimmedNonEmptyStringvalues for the assigned profile id, but callers treat those values as authoritative. Inapps/web/src/profiles.ts,assignedProfileIdForProject()returns the raw stored id andisProjectVisibleInActiveProfile()compares it directly togetActiveProfileId(settings). If a profile is renamed/removed or the settings file contains a stale id, every project assigned to that id disappears from every profile instead of falling back todefault. The schema needs to reject or normalize assignments whose value is not present inprofiles.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/profiles.ts around lines 122-127:
`assignedProfileIdForProject` returns a stale profile id when a profile has been removed from `settings.profiles` (or normalized away because its id or name trims to empty). Any project or thread assigned to that orphaned id stops matching the active profile and disappears from the sidebar and command palette instead of falling back to the default profile. The function checks whether `projectProfileAssignments` has an entry for `projectKey`, but it never validates that the stored id is still present in `getAppProfiles(settings)`. Consider resolving the stored id against the current profile set and returning `DEFAULT_PROFILE_ID` when it no longer exists.
Also found in 1 other location(s):
- packages/contracts/src/settings.ts:121 -- `projectProfileAssignments` stores arbitrary `TrimmedNonEmptyString` values for the assigned profile id, but callers treat those values as authoritative. In `apps/web/src/profiles.ts`, `assignedProfileIdForProject()` returns the raw stored id and `isProjectVisibleInActiveProfile()` compares it directly to `getActiveProfileId(settings)`. If a profile is renamed/removed or the settings file contains a stale id, every project assigned to that id disappears from every profile instead of falling back to `default`. The schema needs to reject or normalize assignments whose value is not present in `profiles`.
There was a problem hiding this comment.
🟡 Medium
parseCodexSkillsListResponse falls back to response.data.flatMap((entry) => entry.skills) whenever no entry has entry.cwd === cwd exactly. If Codex reports the current directory with different normalization (symlinked path, case difference, trailing slash, etc.), the fallback pulls in skills from every other cwd, and their toolDependencies are fed into tool-access computation. This causes tool access to be computed against another workspace's skills, wrongly hiding, blocking, or allowing tools. Consider normalizing both entry.cwd and cwd (e.g. path.resolve / case-insensitive comparison on case-insensitive filesystems) before matching, rather than falling back to all entries.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/CodexProvider.ts around line 224:
`parseCodexSkillsListResponse` falls back to `response.data.flatMap((entry) => entry.skills)` whenever no entry has `entry.cwd === cwd` exactly. If Codex reports the current directory with different normalization (symlinked path, case difference, trailing slash, etc.), the fallback pulls in skills from every other `cwd`, and their `toolDependencies` are fed into tool-access computation. This causes tool access to be computed against another workspace's skills, wrongly hiding, blocking, or allowing tools. Consider normalizing both `entry.cwd` and `cwd` (e.g. `path.resolve` / case-insensitive comparison on case-insensitive filesystems) before matching, rather than falling back to all entries.
ApprovabilityVerdict: Needs human review 6 blocking correctness issues found. This PR introduces significant new features (profile-scoped tool access, plan-and-goal interaction mode) with substantial schema changes and runtime behavior modifications across server and client apps. Multiple unresolved review comments identify correctness issues, including a high-severity bug with auto-goal retry logic and several medium-severity problems with mode handling and stale profile states. You can customize Macroscope's approvability policy. Learn more. |


What Changed
Why
UI Changes
Checklist
Note
High Risk
Large cross-cutting change to orchestration, multiple provider adapters, client settings schema, and core chat UX (profiles, tool filtering, auto plan-and-goal); regressions could affect turn starts, provider behavior, or what users see per profile.
Overview
Adds work profiles so the sidebar, command palette, composer provider list, and archived threads only show projects, threads, and provider instances tied to the active profile. Users switch profiles from the sidebar, assign projects when adding them or via context menu, and can scope provider instances to one profile or all profiles.
Introduces tool access policies at global, profile, and project levels (all / inherit / custom allowlists for skills, plugins, and MCP tools). Settings and a project context-menu dialog use a new
ToolAccessPolicyControl; the composer filters$skills through the effective policy for the active project.Adds
plan_and_goalinteraction mode (/plan-goal, three-way mode toggle: build → plan → goal). After a plan is accepted, follow-up can sendgoalObjectivethrough orchestration to providers (Codex sets thread goal; Claude/Cursor/OpenCode treat it like plan mode for permissions).planAndGoalReviewModecan auto-submit goal execution when a plan is ready.Sidebar shows deduplicated subchat rows from collab agent tool activities. UI chrome adds optional app background color and emerald/terminal styling. Contracts gain profile/tool settings, skill
toolDependencies, and extended interaction/turn payloads.Reviewed by Cursor Bugbot for commit 7e27fec. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add profile-scoped tool access, provider assignment, and plan-goal interaction mode
AppProfilesystem: users can create and switch profiles from the sidebar; projects, threads, and provider instances are filtered to the active profile across the sidebar, command palette, and archived threads view.plan_and_goalinteraction mode (alongsideplananddefault) exposed as a/plan-goalslash command in all composers; triggers plan-mode on providers and optionally sets a thread goal viathread/goal/set.ToolAccessPolicyControlUI andresolveEffectiveToolAccessprecedence logic; provider skills visible in the composer are filtered accordingly.ClientSettingswith new fields:activeProfileId,profiles,projectProfileAssignments,providerInstanceProfileAssignments,globalToolAccessPolicy,profileToolAccessPolicies,projectToolAccessPolicies,appBackgroundColor, andplanAndGoalReviewMode.goalObjectivefield end-to-end: from client settings patch through orchestration contracts, provider adapters (Codex, Claude, Cursor, OpenCode), and message creation.plan_and_goalauto-review mode that automatically submits a goal execution follow-up when a plan is ready.ClientSettingsschema gains many new required-with-defaults fields; existing persisted settings missing these fields will decode with defaults, but any custom serialization or test fixtures must be updated.Macroscope summarized 7e27fec.