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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
854 changes: 849 additions & 5 deletions apps/desktop/src/main/__tests__/codexGlobalPlugins.test.ts

Large diffs are not rendered by default.

28 changes: 26 additions & 2 deletions apps/desktop/src/main/maker-host/auth-adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { createLogger } from '../logger.js';
import { prepareCodexGlobalSkillsLinks } from './codex-global-skills.js';
import { prepareCodexGlobalRulesCopy } from './codex-global-rules.js';
import { prepareCodexGlobalPluginsBridge } from './codex-global-plugins.js';
import { DESKTOP_CAPABILITY_ROUTING_POLICY } from './capability-routing.js';
import { prepareSharedGlobalSkillLinks } from './shared-global-skills.js';
import { relinkSharedCodexAuth } from './codex-auth-link.js';
import { claudeOAuthSpawnEnv } from './claude-oauth-spawn-env.js';
Expand Down Expand Up @@ -895,8 +896,15 @@ export class DesktopCodexAuthAdapter implements AuthAdapter {
(r) => ({ ok: true as const, label: 'rules' as const, warnings: r.warnings }),
(err: Error) => ({ ok: false as const, label: 'rules' as const, err }),
),
prepareCodexGlobalPluginsBridge(this.codexHome).then(
(r) => ({ ok: true as const, label: 'plugins' as const, warnings: r.warnings }),
prepareCodexGlobalPluginsBridge(this.codexHome, {
capabilityRouting: DESKTOP_CAPABILITY_ROUTING_POLICY,
}).then(
(r) => ({
ok: true as const,
label: 'plugins' as const,
warnings: r.warnings,
routingFailures: r.routingFailures,
}),
(err: Error) => ({ ok: false as const, label: 'plugins' as const, err }),
),
]);
Expand All @@ -913,6 +921,22 @@ export class DesktopCodexAuthAdapter implements AuthAdapter {
log.warn('Codex global asset warning', { asset: outcome.label, warning });
}
}
if (!pluginsOutcome.ok) {
// Expected cache/config I/O failures are normalized by the bridge and
// gated against the isolated plugin enablement. A rejection here is an
// unexpected invariant failure, so it must remain fail-closed.
throw new Error(
`Cannot start Codex safely because Cindy could not inspect downstream plugin capabilities: ${pluginsOutcome.err.message}`,
Comment thread
zqchris marked this conversation as resolved.
);
}
if (pluginsOutcome.ok && pluginsOutcome.routingFailures.length > 0) {
for (const failure of pluginsOutcome.routingFailures) {
log.error('Codex capability routing enforcement failed', { failure });
}
throw new Error(
`Cannot start Codex safely because Cindy could not isolate a downstream plugin capability: ${pluginsOutcome.routingFailures.join('; ')}`,
);
}
}

/** maker-host 在构造完 codexAgent 后调一次, 注入 dispose 回调。 */
Expand Down
114 changes: 114 additions & 0 deletions apps/desktop/src/main/maker-host/capability-routing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import type { CapabilityRoutingPolicy } from '@cindy/maker-core';

/**
* Product-level arbitration for capability sources that collide inside Cindy.
*
* User/project Skills and normal plugin capabilities remain available. Only a
* named overlapping downstream source is narrowed, and explicit selectors keep
* that source reachable when the user deliberately chooses it.
*/
export const DESKTOP_CAPABILITY_ROUTING_POLICY = {
overrides: [
{
capabilityId: 'feishu',
source: {
kind: 'harness-plugin',
harness: 'claude-code',
surface: 'skill',
id: 'feishu-delegate:message-feishu-coworkers',
artifactId: 'message-feishu-coworkers',
containerId: 'feishu-delegate',
},
invocation: 'explicit-only',
explicitSelectors: [
'/feishu-delegate:message-feishu-coworkers',
],
replacement: {
kind: 'cindy-plugin',
id: 'xd-feishu',
},
reason: 'Natural-language Feishu requests should use the Cindy-connected account.',
},
{
capabilityId: 'feishu',
source: {
kind: 'harness-plugin',
harness: 'codex',
surface: 'skill',
id: 'feishu-delegate:message-feishu-coworkers',
artifactId: 'message-feishu-coworkers',
containerId: 'feishu-delegate@personal',
},
invocation: 'explicit-only',
explicitSelectors: [
'$feishu-delegate:message-feishu-coworkers',
'/feishu-delegate:message-feishu-coworkers',
],
replacement: {
kind: 'cindy-plugin',
id: 'xd-feishu',
},
reason: 'Natural-language Feishu requests should use the Cindy-connected account.',
},
{
capabilityId: 'feishu',
source: {
kind: 'harness-plugin',
harness: 'claude-code',
surface: 'mcp',
id: 'plugin:feishu-delegate:feishu-delegate',
containerId: 'feishu-delegate',
},
invocation: 'explicit-only',
explicitSelectors: [
'/feishu-delegate:message-feishu-coworkers',
],
replacement: {
kind: 'cindy-plugin',
id: 'xd-feishu',
},
reason: 'The downstream Feishu account must not be used without an explicit source choice.',
},
{
capabilityId: 'feishu',
source: {
kind: 'harness-plugin',
harness: 'codex',
surface: 'mcp',
// Codex does not expose plugin provenance in the MCP approval request
// itself. Give the plugin server a Cindy-only runtime name in the
// isolated overlay, then verify its owning pluginId from the preceding
// mcpToolCall item. A user MCP may legally reuse either server name and
// must remain unaffected.
id: 'cindy-routed-feishu-delegate',
artifactId: 'feishu-delegate',
containerId: 'feishu-delegate@personal',
},
invocation: 'explicit-only',
explicitSelectors: [
'$feishu-delegate:message-feishu-coworkers',
'/feishu-delegate:message-feishu-coworkers',
],
replacement: {
kind: 'cindy-plugin',
id: 'xd-feishu',
},
reason: 'The downstream Feishu account must not be used without an explicit source choice.',
},
{
capabilityId: 'computer-use',
source: {
kind: 'harness-plugin',
harness: 'codex',
surface: 'plugin',
id: 'computer-use@openai-bundled',
},
invocation: 'disabled',
replacement: {
kind: 'cindy-host',
id: 'cindy_computer',
},
reason: 'Cindy owns desktop-control enablement, permissions, and execution.',
},
],
} as const satisfies CapabilityRoutingPolicy;
Loading