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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { describe, expect, it } from 'vitest';

import { composePiSystemPrompt } from '../pi-host.js';
import hostSystemPrompt from '../host-system-prompt.md?raw';
import piSystemPrompt from '../pi-system-prompt.md?raw';

describe('composePiSystemPrompt', () => {
it('keeps the shared identity first and appends the Pi language behavior', () => {
Expand All @@ -17,4 +19,10 @@ describe('composePiSystemPrompt', () => {
it('trims sections and omits empty ones', () => {
expect(composePiSystemPrompt(' You are Cindy. ', ' ')).toBe('You are Cindy.');
});

it('does not extend Claude/Codex Skill precedence into Pi', () => {
expect(composePiSystemPrompt(hostSystemPrompt, piSystemPrompt)).not.toContain(
'## Skill source precedence',
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ describe('runtime-configs', () => {
readMemorySettings: () => memorySettings,
}));

const { buildDesktopClaudeRuntimeConfig, desktopCodexRuntimeConfig } = await import('../runtime-configs.js');
const { buildDesktopClaudeRuntimeConfig, desktopCodexRuntimeConfig } = await import(
'../runtime-configs.js'
);

const claudeConfig = buildDesktopClaudeRuntimeConfig(() => 'http://127.0.0.1:1234');

Expand All @@ -76,4 +78,32 @@ describe('runtime-configs', () => {
expect(desktopCodexRuntimeConfig.memoryEnabled).toBe(true);
expect(desktopCodexRuntimeConfig.makerMemoryEnabled).toBe(false);
});

it('places generic Cindy-side Skill precedence in Claude and Codex only', async () => {
vi.doMock('../memory-settings-store.js', () => ({
readMemorySettings: () => memorySettings,
}));

const { buildDesktopClaudeRuntimeConfig, desktopCodexRuntimeConfig } = await import(
'../runtime-configs.js'
);
const claudeConfig = buildDesktopClaudeRuntimeConfig(() => 'http://127.0.0.1:1234');
const prompts = [claudeConfig.systemPrompt, desktopCodexRuntimeConfig.systemPrompt];

for (const prompt of prompts) {
expect(prompt).toContain('## Skill source precedence');
expect(prompt).toMatch(
/Cindy surfaces Skills from its own managed, user, and project\s+sources/u,
);
expect(prompt).toContain('Explicitly selecting the downstream Skill does not waive');
// 来源判定必须落在可观察的清单标注上,并且无标注时 fail-closed 回落到
// 「先跑 Cindy 侧 Skill」,否则这条规则对模型不可执行 (#1650 review)。
expect(prompt).toMatch(/available-Skills listing already labels each Skill/u);
expect(prompt).toMatch(
/no usable source\s+label, treat the Skill as downstream and still run the applicable Cindy-side Skill first/u,
);
// 产品侧只表达来源级规则:不得出现具体 selector、Skill 名或本机路径。
expect(prompt).not.toMatch(/\$[\w:-]+|\/(?:Users|home)\/|[A-Z]:\\/u);
}
});
});
12 changes: 6 additions & 6 deletions apps/desktop/src/main/maker-host/runtime-configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ import { hasClaudeAiOAuth } from './claude-credentials-store.js';
import claudeSystemPrompt from './claude-system-prompt.md?raw';
import codexSystemPrompt from './codex-system-prompt.md?raw';
import hostSystemPrompt from './host-system-prompt.md?raw';
import skillSourcePrecedencePrompt from './skill-source-precedence-prompt.md?raw';
import { readCompactionPct } from './compaction-settings-store.js';
import { readMemorySettings } from './memory-settings-store.js';
import { readSubagentModelSettings } from './subagent-model-settings-store.js';
import { toolchainThreadCapEnv } from './toolchain-thread-cap.js';

// host 层 system prompt 拼接:先 host 共用段 (host-system-prompt.md),再 agent 专属段
// (claude-system-prompt.md / codex-system-prompt.md)。空段被过滤,避免出现孤零零的 \n\n
// Claude / Codex 的 host system prompt:产品身份 → Skill 来源优先级 → agent 专属段
// Skill 优先级不放 host-system-prompt.md,避免把 #1645 的 Claude/Codex 行为扩到 Pi
function composeHostPrompt(agentSpecific: string): string {
return [hostSystemPrompt, agentSpecific]
return [hostSystemPrompt, skillSourcePrecedencePrompt, agentSpecific]
Comment thread
zqchris marked this conversation as resolved.
.map(s => s.trim())
.filter(s => s.length > 0)
.join('\n\n');
Expand Down Expand Up @@ -138,8 +139,7 @@ export function buildDesktopClaudeRuntimeConfig(endpointFn: () => string): Agent
// 核数算,远端机器的资源不归本设置管。设置关闭时为空对象,零影响。
...(ctx.spawnMode === 'remote' ? {} : toolchainThreadCapEnv()),
}),
// xdt-maker 产品级 system prompt 注入:host 共用段 (host-system-prompt.md)
// + Claude 专属段 (claude-system-prompt.md),按顺序拼接后给 maker-core append。
// 产品身份 + Skill 来源优先级 + Claude 专属段,按顺序拼接后给 maker-core append。
systemPrompt: composeHostPrompt(claudeSystemPrompt),
// Maker Memory 需要的 user-data 绝对路径 (maker-core 没 Electron 依赖, 必须 host 注入)。
userDataPath: app.getPath('userData'),
Expand Down Expand Up @@ -237,7 +237,7 @@ export const desktopCodexRuntimeConfig: AgentRuntimeConfig = {
// 附近注释),所以这里仍按 spawnMode 分流让代码自证,不依赖"远端不走本函数"
// 这种会过期的假设(对抗式预审发现)。
behaviorFlags: (ctx) => (ctx.spawnMode === 'remote' ? {} : toolchainThreadCapEnv()),
// host 共用段 (host-system-prompt.md) + Codex 专属段 (codex-system-prompt.md)
// 产品身份 + Skill 来源优先级 + Codex 专属段。
systemPrompt: composeHostPrompt(codexSystemPrompt),
pathPrepends: [bundledRipgrepDir()],
userDataPath: app.getPath('userData'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## Skill source precedence

Cindy surfaces Skills from its own managed, user, and project sources. The downstream agent
harness and its plugins surface Skills of their own. When a Cindy-side Skill and a downstream
Skill both apply to the same piece of work:

1. Load and follow every applicable Cindy-side Skill first.
2. Use the downstream Skill only as a supplement. It must not replace, weaken, bypass, or silently
take over the earlier Skill's workflow or safety gates.
3. Explicitly selecting the downstream Skill does not waive the Cindy-side instructions.
4. Skills that do not overlap remain available normally.

Read the source from how the available-Skills listing already labels each Skill: entries the
harness presents under a plugin, marketplace, or built-in namespace are downstream; the rest come
from Cindy's own sources. This is a source-level rule — when a listing gives no usable source
label, treat the Skill as downstream and still run the applicable Cindy-side Skill first.
Loading