Merge remote-tracking branch 'origin/master' into worktree/fix-minimal-preset-prompt

This commit is contained in:
Yichen Jiang
2026-08-10 21:06:25 +08:00
49 changed files with 909 additions and 92 deletions

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 docs/subsystems/core.md
core.md: 75f55fe5b1837576ba79564b9aee7e289f5f16f4
core.zh.md: 7cd55b41c8f1358a89c6f35a4d3f733ccd8550ee
core.md: ad00c4da7d77b0e1ab4728173b202ebc17fb56a0
core.zh.md: 9c606023c85369643e7148f829526b1f75ea3631

View File

@@ -419,6 +419,45 @@ async resolve(id?: string): Promise<AgentPreset>
*/
async mount(agentCtx: Context, id?: string): Promise<AgentPreset>
/**
* Join one agent to the SAME standing composition another already runs on.
*
* This is how a child agent inherits its parent's capabilities. It is a bind,
* not a mount: the parent's generation is already composed, so the child gets
* that exact instance — the same plugin objects, the same tool registrations,
* the same prompt sections. Re-resolving the parent's preset by id instead
* would re-read the roster, and a composition file edited since the parent
* started would hand the child a DIFFERENT generation than the one its
* parent's history was produced under (and a preset deleted since would fail
* the child outright while its parent keeps running).
*
* Synchronous, and with no composition failure mode of its own — it reads no
* roster, mounts nothing, and touches no file — which is what lets a child
* creation window use it: the two in-process subagent drivers compose their
* children inside a synchronous `setup`. It still rejects a caller error, as
* the `@throws` below record.
*
* A parent that joined no preset — a rosterless deployment — yields no join
* and no error: there, the model-facing rows sit in the host composition and
* the child already sees them through the global layer.
* @param agentCtx - the joining agent's scope context.
* @param parentCtx - the scope context of the agent whose composition to join.
* @returns the preset id joined, or undefined when the parent joined none.
* @throws when `agentCtx` carries no scope, or has already joined a preset.
*/
composeFrom(agentCtx: Context, parentCtx: Context): string | undefined
/**
* The preset one live agent runs on.
*
* Read from the live scope chain rather than from the session, so it answers
* for an agent whose session has not recorded a preset yet — a child agent
* whose durable header is being built from its parent's composition.
* @param agentCtx - the agent's scope context.
* @returns the preset id, or undefined when the agent joined none.
*/
composedPreset(agentCtx: Context): string | undefined
/**
* Read one preset's composition text.
* @param id - the preset id.

View File

@@ -427,6 +427,45 @@ async resolve(id?: string): Promise<AgentPreset>
*/
async mount(agentCtx: Context, id?: string): Promise<AgentPreset>
/**
* Join one agent to the SAME standing composition another already runs on.
*
* This is how a child agent inherits its parent's capabilities. It is a bind,
* not a mount: the parent's generation is already composed, so the child gets
* that exact instance — the same plugin objects, the same tool registrations,
* the same prompt sections. Re-resolving the parent's preset by id instead
* would re-read the roster, and a composition file edited since the parent
* started would hand the child a DIFFERENT generation than the one its
* parent's history was produced under (and a preset deleted since would fail
* the child outright while its parent keeps running).
*
* Synchronous, and with no composition failure mode of its own — it reads no
* roster, mounts nothing, and touches no file — which is what lets a child
* creation window use it: the two in-process subagent drivers compose their
* children inside a synchronous `setup`. It still rejects a caller error, as
* the `@throws` below record.
*
* A parent that joined no preset — a rosterless deployment — yields no join
* and no error: there, the model-facing rows sit in the host composition and
* the child already sees them through the global layer.
* @param agentCtx - the joining agent's scope context.
* @param parentCtx - the scope context of the agent whose composition to join.
* @returns the preset id joined, or undefined when the parent joined none.
* @throws when `agentCtx` carries no scope, or has already joined a preset.
*/
composeFrom(agentCtx: Context, parentCtx: Context): string | undefined
/**
* The preset one live agent runs on.
*
* Read from the live scope chain rather than from the session, so it answers
* for an agent whose session has not recorded a preset yet — a child agent
* whose durable header is being built from its parent's composition.
* @param agentCtx - the agent's scope context.
* @returns the preset id, or undefined when the agent joined none.
*/
composedPreset(agentCtx: Context): string | undefined
/**
* Read one preset's composition text.
* @param id - the preset id.

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 docs/subsystems/tools.md
tools.md: f8d86704a2237219530c8c23b46a68383458e1cf
tools.zh.md: 87269e5532b0cdfb0a38501d7b98c9986665df1d
tools.md: 6ff2d967c5631d096dd78236ffd0383ddb2b0493
tools.zh.md: 82ade5d8d4117387138296cf54fbc8e88ad335e7

View File

@@ -150,19 +150,20 @@ type InferArgs<S> = InferProperties<S, []>
Registration is a trusted same-process contract. The registry borrows the typed definition as readonly input, requires `output`, validates its raw schema, and checks semantic requirements such as a positive finite `timeoutMs`; `schemas()` constructs the model-facing projection when building a request, so execution and presentation share one resolved definition without leaking callbacks onto the wire.
## `ToolRestriction` — one scope's live global filter
## `ToolRestriction` — one scope's live filter over what it inherits
`ToolRestriction` applies only to the live deployment-global tool layer. The registry compiles readonly names into private sets, intersects multiple restrictions, then overlays scope-local tools. A deny-only filter admits later unlisted globals, while an allow-list excludes them.
`ToolRestriction` applies to the tools a scope inherits: the deployment-global layer plus every ancestor scope on its chain. The registry compiles readonly names into private sets, intersects multiple restrictions, then overlays the scope's OWN registrations, which stay exempt so a delegated child keeps the tools it answers through. A deny-only filter admits later unlisted inherited tools, while an allow-list excludes them.
```ts type-equiv
/**
* Per-scope filter over global tools. Restrictions intersect and do not affect
* scoped registrations or the reserved Code Mode transport.
* Per-scope filter over the tools a scope INHERITS — the global layer and
* every ancestor layer on its chain. Restrictions intersect, and do not affect
* the scope's own registrations or the reserved Code Mode transport.
*/
interface ToolRestriction {
/** Global tool names that stay visible; everything else is removed. */
/** Inherited tool names that stay visible; every other inherited one is removed. */
readonly allow?: readonly string[]
/** Global tool names removed from visibility. */
/** Inherited tool names removed from visibility. */
readonly deny?: readonly string[]
}
```
@@ -565,7 +566,7 @@ async execute(exec: ToolExecutionInput): Promise<ToolExecutionResult>
Types: [ScopeKey](scope.md)
Source: [`packages/core/tools/src/index.ts:760`](../../packages/core/tools/src/index.ts)
Source: [`packages/core/tools/src/index.ts:761`](../../packages/core/tools/src/index.ts)
<a id="tools-events"></a>

View File

@@ -150,19 +150,20 @@ type InferArgs<S> = InferProperties<S, []>
注册是一项受信任的同进程约定。注册表以 readonly 输入借用已类型化定义,要求它声明 `output`,校验其原始 schema并检查 `timeoutMs` 必须为正有限值等语义要求;`schemas()` 在构建请求时生成面向模型的投影,使执行和展示共享同一份已解析定义,而不会将回调泄漏到协议上。
## `ToolRestriction` — 单个作用域的实时全局过滤器
## `ToolRestriction` — 单个作用域对其继承内容的实时过滤器
`ToolRestriction` 作用于实时的部署全局工具层。注册表将 readonly 名称编译为私有集合,对多个限制取交集,再叠加作用域本地工具。仅 deny 的过滤器允许后续未列出的全局工具通过,而 allow 列表则排除它们。
`ToolRestriction` 作用于该作用域继承来的工具:部署全局层,加上其链上的每个祖先作用域。注册表将 readonly 名称编译为私有集合,对多个限制取交集,再叠加作用域**自身**的注册——后者不受约束,因此被委派的子 agent 会保留其回报所依赖的工具。仅 deny 的过滤器允许后续未列出的继承工具通过,而 allow 列表则排除它们。
```ts type-equiv
/**
* Per-scope filter over global tools. Restrictions intersect and do not affect
* scoped registrations or the reserved Code Mode transport.
* Per-scope filter over the tools a scope INHERITS — the global layer and
* every ancestor layer on its chain. Restrictions intersect, and do not affect
* the scope's own registrations or the reserved Code Mode transport.
*/
interface ToolRestriction {
/** Global tool names that stay visible; everything else is removed. */
/** Inherited tool names that stay visible; every other inherited one is removed. */
readonly allow?: readonly string[]
/** Global tool names removed from visibility. */
/** Inherited tool names removed from visibility. */
readonly deny?: readonly string[]
}
```
@@ -565,7 +566,7 @@ async execute(exec: ToolExecutionInput): Promise<ToolExecutionResult>
Types: [ScopeKey](scope.md)
Source: [`packages/core/tools/src/index.ts:760`](../../packages/core/tools/src/index.ts)
Source: [`packages/core/tools/src/index.ts:761`](../../packages/core/tools/src/index.ts)
<a id="tools-events"></a>