fix(python-sdk): make the minimal profile an explicit allowlist

Give the SDK JSON-RPC server a per-root tool filter and let deployments mark the configured persona as the complete system prompt. The checked-in minimal overlay now names only bash and str_replace_editor, so later global tools and unrelated guidance from dsh-base cannot appear implicitly.

Keep the shared SDK host services and packaged Web capability intact. Only workspace instructions, compaction, and the conflicting one-shot Bash row remain disabled. Unit coverage pins the configuration paths, and a real dsh profile smoke proves the assembled prompt and exact two-tool request.
This commit is contained in:
Tianyi Cui
2026-08-24 17:28:28 +08:00
parent d801f262d8
commit c2ad69344f
32 changed files with 327 additions and 93 deletions
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/core/system-prompt/README.md
README.md: d750a507e628e7609af542227e4528d4d4934ce8
README.zh.md: ec5b32d742b96c8044a3707c35f15e30ba642b4f
README.md: a52aa3e4c2782993fed5a525cc827aba4e3eaeb0
README.zh.md: cf0ba43aa2f2f47ea61ef13c74fbf182fbd9f2ee
+1
View File
@@ -11,6 +11,7 @@ System prompt assembly registry. Plugins contribute ordered sections, tool schem
| `includeHarnessIdentity` | `true` | Include the fixed `You are an AI agent powered by DeepSeek Harness.` order-100 opener. Set false only when a compatibility deployment owns the complete system prompt. |
| `includeRuntimeContext` | `true` | Include ordered dynamic contexts in assembly. When false, context providers are not evaluated and contexts added by `system-prompt/assemble` listeners are discarded after the waterfall; other services and their enforcement remain active. |
| `persona` | `''` | The global deployment-persona default: the ONE config-authored prompt fragment, rendered as the order-0 `deployment:persona` section unless an agent-scoped contribution shadows it. A template — complete `{{…}}` groups are interpreted strictly against the registered variables (the shipped loop registers `{{model}}`/`{{cwd}}`), with no escape syntax for literal braces yet. Empty ⇒ the section is dropped at render. |
| `personaComplete` | `false` | Treat `persona` as the complete system prompt after assembly. Other sections remain registered but are omitted from model requests; tool schemas and variables remain available. |
| `toolOrder` | — | Explicit model-facing tool order, as a list of `ToolSchema.name`s with one `'<unlisted-tools>'` rest entry (`TOOL_ORDER_REST`): listed tools take their listed position, unlisted tools land at the rest entry in lexicographic name order. Absent ⇒ plain lexicographic name order. Applied to the collected tools BEFORE the `system-prompt/assemble` waterfall — like the sections' `order` sort, it canonicalizes what the registry contributed (registration order is a plugin-load artifact), and a waterfall listener that mutates the list owns the determinism of what it emits. Misconfiguration fails loud: a list without exactly one rest entry, or with duplicates, throws at load; a listed name with no registered tool rejects every `assemble()`; a tool provider returning the reserved rest-entry name also rejects. Under the shipped loop the turn fails before any model request. Why a central list and not per-plugin weights: [Explicit model-facing tool order](../../../.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md). |
## Service: `SystemPrompt` (ctx key: `systemPrompt`)
+1
View File
@@ -11,6 +11,7 @@
| `includeHarnessIdentity` | `true` | 是否包含顺序为 −100 的固定开场白 `You are an AI agent powered by DeepSeek Harness.`。仅当兼容性部署拥有完整系统提示词时设为 false。 |
| `includeRuntimeContext` | `true` | 是否在组装中包含有序动态上下文。设为 false 时不会求值上下文提供方,并会在 waterfall 后丢弃 `system-prompt/assemble` 监听器添加的上下文;其他服务及其强制机制仍然生效。 |
| `persona` | `''` | 全局部署 persona 默认值:唯一由配置提供的提示词片段,渲染为顺序为 0 的 `deployment:persona` 段,除非 agent 作用域的贡献将其遮蔽。它是模板,完整的 `{{…}}` 组会严格按已注册变量解释(随附循环注册 `{{model}}`/`{{cwd}}`),目前没有表达字面量花括号的转义语法。为空 ⇒ 渲染时删除该段。 |
| `personaComplete` | `false` | 在组装后将 `persona` 作为完整系统提示词。其他段仍保持注册,但不会进入模型请求;工具 schema 与变量仍然可用。 |
| `toolOrder` | 无 | 显式指定面向模型的工具顺序。该列表由 `ToolSchema.name` 组成,并且必须恰好包含一个 `'<unlisted-tools>'` 其余项标记(`TOOL_ORDER_REST`):已列工具按列表位置排列,未列工具则按名称字典序插入该标记所在的位置。缺席 ⇒ 直接按名称字典序排列。该顺序会在 `system-prompt/assemble` waterfall(瀑布式事件)之前应用于已收集的工具。与段的 `order` 排序一样,它会规范化注册表贡献的内容;注册顺序只是插件加载时序的产物。修改列表的 waterfall 监听器对其输出的确定性负责。配置错误会明确失败:列表没有恰好一个其余项或存在重复项,会在加载时抛出;已列名称没有对应已注册工具,会使每次 `assemble()` 被拒绝;工具提供方返回保留的其余项名称也会被拒绝。在随附循环下,轮次会在任何模型请求前失败。为何采用中心列表而非每插件权重,见[显式面向模型工具顺序](../../../.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.zh.md)。 |
## 服务:`SystemPrompt`ctx 键:`systemPrompt`
+4
View File
@@ -193,6 +193,8 @@ export interface Config {
* `deployment:persona` shadows it; `{{variable}}` references are strict.
*/
persona?: string
/** Treat the deployment persona as the complete system prompt (default false). */
personaComplete?: boolean
/**
* Model-facing tool names in order, with {@link TOOL_ORDER_REST} exactly once.
* Invalid fields fail at load and unknown names fail at assembly; known names
@@ -340,6 +342,7 @@ export class SystemPrompt extends Service {
includeHarnessIdentity: z.boolean().default(true),
includeRuntimeContext: z.boolean().default(true),
persona: z.string().default(''),
personaComplete: z.boolean().default(false),
// Preserve omission because an explicit empty order lacks the rest marker.
toolOrder: z.array(z.string()).default(undefined as unknown as string[]),
})
@@ -366,6 +369,7 @@ export class SystemPrompt extends Service {
order: PERSONA_ORDER,
// The fallback narrows the optional input type; the schema already defaults it.
text: config.persona ?? '',
complete: config.personaComplete ?? false,
})
if (!(config.includeRuntimeContext ?? true)) this.suppressRuntimeContext()
}
@@ -49,6 +49,21 @@ describe('SystemPrompt', () => {
expect(renderPrompt(assembly)).toBe('You are a helpful software engineer assistant.')
})
it('can make the deployment persona the complete system prompt', async () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt, {
persona: 'You are a focused SDK agent.',
personaComplete: true,
})
ctx.systemPrompt.section({ name: 'tool:future', order: 100, text: 'Future tool guidance.' })
const assembly = await ctx.systemPrompt.assemble()
expect(assembly.sections).toEqual([
{ name: 'deployment:persona', text: 'You are a focused SDK agent.' },
])
expect(renderPrompt(assembly)).toBe('You are a focused SDK agent.')
})
it('can suppress runtime context without evaluating providers or accepting waterfall additions', async () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt, { includeRuntimeContext: false })