fix(plugin): address review on the preset-patch revert
Let a title provider replace one that is still draining, so switching the titles bundle during an in-flight request no longer leaves no provider. Omit group container rows from bundle inventories, since they have no plugin entry to switch. Drop the obsolete preset-target product-use case, fix the Claude Code row id example, and refresh stale Agent Note text.
This commit is contained in:
1 parent
b173f3e2a2
commit
a8e9e9b546
19 files changed
+68
-36
No files matched your search
+4
-4
@@ -9,11 +9,11 @@
|
||||
en: a590bcef6c9760c0
|
||||
zh: 19e6f61c2c7d8d3b
|
||||
/agent-note-discover-official-bundles-without-installing-their-runtimes/decision:
|
||||
en: 3a3dbad398a418be
|
||||
zh: 9ef707e746cdfd94
|
||||
en: f0fabc0d4fbd1178
|
||||
zh: cf19938a7a7dea93
|
||||
/agent-note-discover-official-bundles-without-installing-their-runtimes/alternatives-considered:
|
||||
en: 42cb0f808a145871
|
||||
zh: 225012615d1ecdf5
|
||||
/agent-note-discover-official-bundles-without-installing-their-runtimes/consequences:
|
||||
en: 899b07d90024e019
|
||||
zh: 01d22ac4b2444feb
|
||||
en: 293286dcd7e3325e
|
||||
zh: c6c15212e85d050b
|
||||
@@ -18,7 +18,7 @@ The running installation owns the target. Published applications offer the exact
|
||||
|
||||
Every Official package remains in the DSH release family. Packed DSH dependencies, optional dependencies, and peers use the exact family version. Publication prerequisites place catalog packages before the DSH package advertising them without adding their runtimes to DSH production dependencies. Catalog verification rejects missing metadata, unsupported membership, and stale generated content.
|
||||
|
||||
The [shipped optional-bundle policy](../process/2026-09-15-shipped-optional-bundles.md) still owns lightweight installation-provided bundles. The [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md) owns shared patch and preset-generation behavior. Native bundles require the full Web preset targets; custom profiles mount their providers and tools explicitly. The [identity compatibility decision](2026-10-06-web-desktop-plugin-identity-compatibility.md) owns retained names, selectors, and deferred naming cleanup.
|
||||
The [shipped optional-bundle policy](../process/2026-09-15-shipped-optional-bundles.md) still owns lightweight installation-provided bundles. The [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md) owns shared patch behavior and global optional tools. Native bundles insert ordinary Host rows, so any profile can select them; profiles that keep the package without its bundle mount the same rows explicitly. The [identity compatibility decision](2026-10-06-web-desktop-plugin-identity-compatibility.md) owns retained names, selectors, and deferred naming cleanup.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -34,4 +34,4 @@ The [shipped optional-bundle policy](../process/2026-09-15-shipped-optional-bund
|
||||
|
||||
## Consequences
|
||||
|
||||
Registry delivery requires a reachable registry; development delivery requires a prepared, built checkout and normal rebuild/restart discipline. Equal source version strings do not certify identical build outputs. Off only deselects; Remove remains separate and respects retained preset modules. Activation failure can retain the saved dependency and selection; there is no rollback. Offline metadata, release pins, source-target selection, same-version source mismatches, clean installed artifacts, and release ordering have dedicated checks. Native-provider authentication is not part of catalog discovery.
|
||||
Registry delivery requires a reachable registry; development delivery requires a prepared, built checkout and normal rebuild/restart discipline. Equal source version strings do not certify identical build outputs. Off only deselects; Remove remains separate and refuses while the bundle's rows are in use. Activation failure can retain the saved dependency and selection; there is no rollback. Offline metadata, release pins, source-target selection, same-version source mismatches, clean installed artifacts, and release ordering have dedicated checks. Native-provider authentication is not part of catalog discovery.
|
||||
@@ -18,7 +18,7 @@ Status: implemented
|
||||
|
||||
所有 Official 包仍属于 DSH 发布家族。打包后的 DSH 依赖、可选依赖与 peer 均使用家族精确版本。发布前置关系要求先发布目录包,再发布宣传它们的 DSH 包,同时不把这些运行时加入 DSH 生产依赖。目录校验拒绝缺失的元信息、不受支持的成员和过期生成内容。
|
||||
|
||||
[随附可选 bundle 策略](../process/2026-09-15-shipped-optional-bundles.zh.md)仍管理安装随附的轻量 bundle。[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)管理共享 patch 与预设代际行为。原生 bundle 要求完整 Web 预设目标存在;自定义 profile 显式挂载提供方和工具。[身份兼容决策](2026-10-06-web-desktop-plugin-identity-compatibility.zh.md)管理保留名称、选择器和暂缓的命名清理。
|
||||
[随附可选 bundle 策略](../process/2026-09-15-shipped-optional-bundles.zh.md)仍管理安装随附的轻量 bundle。[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)管理共享 patch 行为与全局可选工具。原生 bundle 插入普通 Host 行,因此任何 profile 都可以选择它们;保留包但不选择其 bundle 的 profile 显式挂载相同的行。[身份兼容决策](2026-10-06-web-desktop-plugin-identity-compatibility.zh.md)管理保留名称、选择器和暂缓的命名清理。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -34,4 +34,4 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
注册表交付要求注册表可达;开发交付要求准备并构建 checkout,遵循正常的重新构建和重启流程。源码版本字符串相同不证明构建输出相同。关闭仅取消选择;移除保持独立,并尊重预设仍保留的模块。激活失败可以保留已保存的依赖和选择,不加入回滚。离线元信息、发布版本锁定、源码目标选择、同版本来源差异、干净安装产物和发布顺序有专门检查。原生提供方认证不属于目录发现过程。
|
||||
注册表交付要求注册表可达;开发交付要求准备并构建 checkout,遵循正常的重新构建和重启流程。源码版本字符串相同不证明构建输出相同。关闭仅取消选择;移除保持独立,并在 bundle 的行仍在使用时拒绝执行。激活失败可以保留已保存的依赖和选择,不加入回滚。离线元信息、发布版本锁定、源码目标选择、同版本来源差异、干净安装产物和发布顺序有专门检查。原生提供方认证不属于目录发现过程。
|
||||
+4
-4
@@ -15,8 +15,8 @@
|
||||
en: 4c428f65ef611d07
|
||||
zh: 1fdc5467b0203d08
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/alternatives-considered:
|
||||
en: 1bf03723fea6c9a4
|
||||
zh: 2d0d2fc997f7c022
|
||||
en: 65af55b5775c9bc5
|
||||
zh: 2ea75b3fd33ac0c2
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/consequences:
|
||||
en: 60f7c823b5d2ed6f
|
||||
zh: 75f375487cab3c56
|
||||
en: e8e96b0354f1a3c6
|
||||
zh: ad1bfe3600e14ff1
|
||||
+2
-2
@@ -31,10 +31,10 @@ These naming changes are deferred, not prerequisites for experimental classifica
|
||||
|
||||
**Publish aliases or keep duplicate compatibility rows.** Parallel identities need continuing resolution and removal rules and can register the same capability twice. Retaining the established identity avoids that additional mechanism.
|
||||
|
||||
**Keep provider-only bundles through additional wrapper packages.** Web and Desktop already provide the required presets. Extra delivery packages would preserve compositions outside the selected compatibility scope and increase catalog and release maintenance.
|
||||
**Keep provider-only bundles through additional wrapper packages.** Native bundles insert ordinary Host rows that compose into any profile. Extra delivery packages would preserve compositions outside the selected compatibility scope and increase catalog and release maintenance.
|
||||
|
||||
## Consequences
|
||||
|
||||
The repository carries a bounded naming-exception map and tests that classify retained names through direct imports, aliases, transitive dependencies, and packed or bundled artifacts. An unprefixed name does not grant admission to the default product. Existing row identities have Web/Desktop composition coverage for user overrides, disabled state, and preset replacement. No general alias or automatic profile migration mechanism is introduced.
|
||||
The repository carries a bounded naming-exception map and tests that classify retained names through direct imports, aliases, transitive dependencies, and packed or bundled artifacts. An unprefixed name does not grant admission to the default product. Existing row identities have Web/Desktop composition coverage for user overrides and disabled state. No general alias or automatic profile migration mechanism is introduced.
|
||||
|
||||
The [classification decision](../process/2026-10-05-product-package-classification.md), [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md), and [on-demand discovery decision](2026-10-05-official-on-demand-bundles.md) retain their separate responsibilities. Released Session generations and persisted hook event names remain protected independently of this profile compatibility scope.
|
||||
+2
-2
@@ -31,10 +31,10 @@ Status: implemented
|
||||
|
||||
**发布别名或保留重复兼容行。** 并行身份需要持续维护解析和移除规则,还可能重复注册同一能力。保留既有身份可以避免增加这种机制。
|
||||
|
||||
**通过额外包装包保留仅提供方 bundle。** Web 和 Desktop 已提供所需预设。额外交付包会保留选定兼容范围之外的组合,并增加目录和发布维护成本。
|
||||
**通过额外包装包保留仅提供方 bundle。** 原生 bundle 插入普通 Host 行,可组合到任何 profile。额外交付包会保留选定兼容范围之外的组合,并增加目录和发布维护成本。
|
||||
|
||||
## 后果
|
||||
|
||||
仓库维护范围明确的命名例外映射,并测试通过直接导入、别名、传递依赖及打包产物识别这些保留名称。没有实验前缀的名称不因此获准进入默认产品。既有行身份具有覆盖用户配置、禁用状态和预设替换的 Web/Desktop 组合测试。不引入通用别名或自动 profile 迁移机制。
|
||||
仓库维护范围明确的命名例外映射,并测试通过直接导入、别名、传递依赖及打包产物识别这些保留名称。没有实验前缀的名称不因此获准进入默认产品。既有行身份具有覆盖用户配置和禁用状态的 Web/Desktop 组合测试。不引入通用别名或自动 profile 迁移机制。
|
||||
|
||||
[分类决策](../process/2026-10-05-product-package-classification.zh.md)、[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)和[按需发现决策](2026-10-05-official-on-demand-bundles.zh.md)继续承担各自独立的职责。已发布 Session 代际和持久化 hook 事件名的保护独立于此 profile 兼容范围。
|
||||
@@ -193,7 +193,8 @@ async refresh(session: Session, signal?: AbortSignal): Promise<SessionTitleSnaps
|
||||
|
||||
/**
|
||||
* Register the sole optional title provider. Disposal aborts its pending and
|
||||
* active work before another provider may register.
|
||||
* active work; a replacement may register once disposal has started, and the
|
||||
* closing provider's late results never commit.
|
||||
* @param provider - provider identity, cadence, and generation function.
|
||||
* @returns exact Cordis effect disposer, which settles after active calls quiesce.
|
||||
*/
|
||||
|
||||
@@ -193,7 +193,8 @@ async refresh(session: Session, signal?: AbortSignal): Promise<SessionTitleSnaps
|
||||
|
||||
/**
|
||||
* Register the sole optional title provider. Disposal aborts its pending and
|
||||
* active work before another provider may register.
|
||||
* active work; a replacement may register once disposal has started, and the
|
||||
* closing provider's late results never commit.
|
||||
* @param provider - provider identity, cadence, and generation function.
|
||||
* @returns exact Cordis effect disposer, which settles after active calls quiesce.
|
||||
*/
|
||||
|
||||
@@ -690,7 +690,8 @@ export class PluginManager extends TypertRemoteService {
|
||||
const rows: BundleRowInfo[] = []
|
||||
const packages = this.ctx.get('pluginPackages')
|
||||
for (const row of flatten(composeEntries([patches.filter(item => item.insert !== undefined)]))) {
|
||||
if (typeof row.id !== 'string' || typeof row.name !== 'string') continue
|
||||
// Group rows are containers without a plugin inventory entry; their children carry the switches.
|
||||
if (typeof row.id !== 'string' || typeof row.name !== 'string' || row.group === true) continue
|
||||
const active = live.get(row.id)
|
||||
const entryId = active?.entryId
|
||||
const base = active?.baseUrl ?? pathToFileURL(join(dir, 'package.json')).href
|
||||
|
||||
@@ -761,6 +761,7 @@ it('addresses children inside profile groups and marks ambiguous ids read-only',
|
||||
config: [{ id: 'child', name: './plugin.mjs', config: { service: 'child' } }] }])
|
||||
expect(await manager.setBundleEnabled('grouped', true)).toMatchObject({ application: 'applied' })
|
||||
expect((await manager.listPlugins()).find(row => row.patchId === 'child')).toBeDefined()
|
||||
expect((await manager.listBundles()).find(item => item.name === 'grouped')?.rows.map(row => row.rowId)).toEqual(['child'])
|
||||
const entries = composeEntries([readProfilePatches('test', profile)])
|
||||
const duplicate = entries.find(row => row.id === 'managed')!
|
||||
writeFileSync(profile.patchPath, JSON.stringify([{ insert: [duplicate] }]))
|
||||
|
||||
@@ -2522,7 +2522,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
},
|
||||
{
|
||||
signature: 'register(provider: SessionTitleProvider): () => Promise<void>',
|
||||
description: 'Register the sole optional title provider. Disposal aborts its pending and active work before another provider may register.',
|
||||
description: 'Register the sole optional title provider. Disposal aborts its pending and active work; a replacement may register once disposal has started, and the closing provider\'s late results never commit.',
|
||||
parameters: [{ name: 'provider', description: 'provider identity, cadence, and generation function.' }],
|
||||
returns: 'exact Cordis effect disposer, which settles after active calls quiesce.',
|
||||
},
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
en: f72239b38a91783b
|
||||
zh: 07e22375c09906a6
|
||||
/deepseek-ai-dsh-session-title/use-this-package/adding-a-provider:
|
||||
en: 909209d339538963
|
||||
zh: fb6350642ec30cc1
|
||||
en: 2ed8217f95490c73
|
||||
zh: e3456cb027ce3fcc
|
||||
/deepseek-ai-dsh-session-title/use-this-package/reading-titles:
|
||||
en: 34148c23c20ee6f4
|
||||
zh: b0d10677722f3f63
|
||||
|
||||
@@ -54,7 +54,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a
|
||||
|
||||
### Adding a provider
|
||||
|
||||
One optional asynchronous provider may be registered through `ctx.sessionTitle.register(provider)`; a second registration throws. The shipped model-backed providers are [first-prompt](../session-title-first-prompt-llm/README.md) and [all-prompts](../../experimental/session-title-all-prompts-llm/README.md), both using the shared [LLM generation policy](../session-title-llm/README.md). A provider starts only after a marked loop-built request's exact route matches the logged `request/header`, and a newer revision supersedes and aborts older work. For unchanged routes, an active marked request starts pending automatic generation without a new header event; accepted human messages may follow the current `step/start`.
|
||||
One optional asynchronous provider may be registered through `ctx.sessionTitle.register(provider)`; a second registration throws until the first provider's disposal starts, so a replacement can activate while the previous provider drains, and the previous provider's late results never commit. The shipped model-backed providers are [first-prompt](../session-title-first-prompt-llm/README.md) and [all-prompts](../../experimental/session-title-all-prompts-llm/README.md), both using the shared [LLM generation policy](../session-title-llm/README.md). A provider starts only after a marked loop-built request's exact route matches the logged `request/header`, and a newer revision supersedes and aborts older work. For unchanged routes, an active marked request starts pending automatic generation without a new header event; accepted human messages may follow the current `step/start`.
|
||||
|
||||
### Reading titles
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ kind: "package-reference"
|
||||
|
||||
### 添加提供方
|
||||
|
||||
可选异步提供方可通过 `ctx.sessionTitle.register(provider)` 注册一个;第二次注册会立即抛出。随附的模型支持提供方是[首消息](../session-title-first-prompt-llm/README.zh.md)与[全消息](../../experimental/session-title-all-prompts-llm/README.zh.md),两者都使用共享的 [LLM(大语言模型)生成策略](../session-title-llm/README.zh.md)。提供方只有在带标记、由循环构建的请求的确切路由与已记录 `request/header` 匹配时才启动,较新的修订会取代并中止旧工作。路由不变时,活跃步骤中带标记的请求会启动待处理的自动生成,无需新的 header 事件;已接受的人类消息可以位于当前 `step/start` 之后。
|
||||
可选异步提供方可通过 `ctx.sessionTitle.register(provider)` 注册一个;在前一个提供方开始释放之前,第二次注册会立即抛出。因此替代提供方可以在前一个提供方排空期间激活,前一个提供方迟到的结果不会提交。随附的模型支持提供方是[首消息](../session-title-first-prompt-llm/README.zh.md)与[全消息](../../experimental/session-title-all-prompts-llm/README.zh.md),两者都使用共享的 [LLM(大语言模型)生成策略](../session-title-llm/README.zh.md)。提供方只有在带标记、由循环构建的请求的确切路由与已记录 `request/header` 匹配时才启动,较新的修订会取代并中止旧工作。路由不变时,活跃步骤中带标记的请求会启动待处理的自动生成,无需新的 header 事件;已接受的人类消息可以位于当前 `step/start` 之后。
|
||||
|
||||
### 读取标题
|
||||
|
||||
|
||||
@@ -464,13 +464,14 @@ export class SessionTitleService extends Service {
|
||||
|
||||
/**
|
||||
* Register the sole optional title provider. Disposal aborts its pending and
|
||||
* active work before another provider may register.
|
||||
* active work; a replacement may register once disposal has started, and the
|
||||
* closing provider's late results never commit.
|
||||
* @param provider - provider identity, cadence, and generation function.
|
||||
* @returns exact Cordis effect disposer, which settles after active calls quiesce.
|
||||
*/
|
||||
register(provider: SessionTitleProvider): () => Promise<void> {
|
||||
this.validateProvider(provider)
|
||||
if (this.registration !== undefined) {
|
||||
if (this.registration !== undefined && !this.registration.closing) {
|
||||
throw new Error(`session-title provider "${this.registration.provider.id}" is already registered`)
|
||||
}
|
||||
const registration: ProviderRegistration = {
|
||||
|
||||
@@ -289,6 +289,41 @@ describe('SessionTitleService Provider lifecycle', () => {
|
||||
await disposeReplacement()
|
||||
})
|
||||
|
||||
it('accepts a replacement while the previous provider drains and discards its late result', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(SessionTitleService, CONFIG)
|
||||
const pending = deferred<SessionTitleProviderResult>()
|
||||
const dispose = ctx.sessionTitle.register({
|
||||
id: SessionTitleProviderId('retiring'),
|
||||
automatic: 'all-prompts',
|
||||
generate: () => pending.promise,
|
||||
})
|
||||
const session = ctx.sessions.create(SessionId('replace-while-draining'))
|
||||
session.append('turn/start', { turn: 1 })
|
||||
const message = appendHumanPrompt(session, 'Generate this title')
|
||||
await settle()
|
||||
appendRoute(session)
|
||||
await settle()
|
||||
|
||||
const disposal = dispose()
|
||||
const disposeReplacement = ctx.sessionTitle.register({
|
||||
id: SessionTitleProviderId('replacement'),
|
||||
automatic: 'all-prompts',
|
||||
generate: async () => ({ title: 'replacement', messageSeqs: [message.seq] }),
|
||||
})
|
||||
expect(() => ctx.sessionTitle.register({
|
||||
id: SessionTitleProviderId('duplicate'),
|
||||
automatic: 'first-prompt',
|
||||
generate: async () => ({ title: 'duplicate', messageSeqs: [message.seq] }),
|
||||
})).toThrow(/"replacement" is already registered/)
|
||||
pending.resolve({ title: 'stale provider result', messageSeqs: [message.seq] })
|
||||
await disposal
|
||||
expect(ctx.sessionTitle.get(session)?.title).not.toBe('stale provider result')
|
||||
await disposeReplacement()
|
||||
})
|
||||
|
||||
it('supersedes an older all-messages revision and cannot commit an ignored abort', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
|
||||
@@ -64,7 +64,7 @@ The bundle inserts `tool-subagent-claude-code` as a Host row, so `subagent_claud
|
||||
- insert:
|
||||
- id: subagent-claude-code
|
||||
name: '@deepseek-ai/dsh-subagent-claude-code'
|
||||
- id: tool-subagent-claude
|
||||
- id: tool-subagent-claude-code
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: claude-code
|
||||
|
||||
@@ -64,7 +64,7 @@ Bundle 将 `tool-subagent-claude-code` 作为 Host 条目插入,因此 `subage
|
||||
- insert:
|
||||
- id: subagent-claude-code
|
||||
name: '@deepseek-ai/dsh-subagent-claude-code'
|
||||
- id: tool-subagent-claude
|
||||
- id: tool-subagent-claude-code
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: claude-code
|
||||
|
||||
@@ -243,14 +243,6 @@ describe('product package use', () => {
|
||||
expect(failures(root, declarations())).toContain(`${candidate} must declare dsh.bundle.patch`)
|
||||
})
|
||||
|
||||
it('rejects an on-demand contribution whose preset target is missing', () => {
|
||||
const root = fixture()
|
||||
pkg(root, candidateDir, candidate, { dsh: { bundle: { patch: './cordis.patch.yml' } } })
|
||||
write(root, `${candidateDir}/cordis.patch.yml`, [{ preset: 'missing-preset', insert: [{ id: 'optional', name: core }] }])
|
||||
profiles(root, [], [candidate])
|
||||
expect(() => verifyProductUse(root, {})).toThrow(/missing-preset/)
|
||||
})
|
||||
|
||||
it('rejects a missing workspace source mapping instead of consulting built exports', () => {
|
||||
const root = fixture()
|
||||
pkg(root, candidateDir, candidate)
|
||||
|
||||
Reference in new issue
Block a user