Просмотр исходного кода

refactor: keep profile launch context data-only and HMR backend-scoped

Turtle 3 дней назад
Родитель
Сommit
438c862fea
94 измененных файлов с 300 добавлено и 1922 удалено
  1. 2 2
      .agents/notes/implemented/architecture/2026-09-14-current-profile-plugin-management.i18n.yaml
  2. 2 4
      .agents/notes/implemented/architecture/2026-09-14-current-profile-plugin-management.md
  3. 2 4
      .agents/notes/implemented/architecture/2026-09-14-current-profile-plugin-management.zh.md
  4. 11 58
      apps/cli/src/profile-boot.ts
  5. 1 1
      apps/cli/tests/telemetry-switch.spec.ts
  6. 2 2
      docs/capability-seams.i18n.yaml
  7. 4 4
      docs/capability-seams.md
  8. 4 4
      docs/capability-seams.zh.md
  9. 2 2
      docs/config-catalog.i18n.yaml
  10. 2 2
      docs/config-catalog.md
  11. 1 1
      docs/config-catalog.zh.md
  12. 2 2
      docs/subsystems/boot.i18n.yaml
  13. 4 26
      docs/subsystems/boot.md
  14. 4 26
      docs/subsystems/boot.zh.md
  15. 2 2
      docs/subsystems/client-modules.i18n.yaml
  16. 1 1
      docs/subsystems/client-modules.md
  17. 1 1
      docs/subsystems/client-modules.zh.md
  18. 2 2
      packages/boot/app-boot/README.i18n.yaml
  19. 1 0
      packages/boot/app-boot/README.md
  20. 1 0
      packages/boot/app-boot/README.zh.md
  21. 1 1
      packages/boot/app-boot/src/index.ts
  22. 66 0
      packages/boot/app-boot/src/profile-context.ts
  23. 0 38
      packages/boot/app-boot/src/profile-runtime.ts
  24. 24 0
      packages/boot/app-boot/tests/profile.spec.ts
  25. 2 2
      packages/boot/plugin-manager/README.i18n.yaml
  26. 9 1
      packages/boot/plugin-manager/README.md
  27. 9 1
      packages/boot/plugin-manager/README.zh.md
  28. 8 2
      packages/boot/plugin-manager/package.json
  29. 41 34
      packages/boot/plugin-manager/src/index.ts
  30. 32 26
      packages/boot/plugin-manager/tests/manager.spec.ts
  31. 6 0
      packages/boot/plugin-manager/tsconfig.json
  32. 2 2
      packages/bundle/base/cordis.patch.yml
  33. 4 1
      packages/bundle/web-app/cordis.patch.yml
  34. 2 2
      packages/client/hmr/README.i18n.yaml
  35. 4 6
      packages/client/hmr/README.md
  36. 4 6
      packages/client/hmr/README.zh.md
  37. 2 5
      packages/client/hmr/package.json
  38. 5 46
      packages/client/hmr/src/client/index.ts
  39. 0 5
      packages/client/hmr/src/index.ts
  40. 0 64
      packages/client/hmr/tests/graph.client.spec.ts
  41. 2 2
      packages/client/modules/README.i18n.yaml
  42. 0 2
      packages/client/modules/README.md
  43. 0 2
      packages/client/modules/README.zh.md
  44. 0 5
      packages/client/modules/src/client/manifest.ts
  45. 1 12
      packages/client/modules/src/client/system.ts
  46. 7 32
      packages/extensions/tool-cordis/src/api-catalog.ts
  47. 1 1
      packages/preset/agent-presets/presets/cordis/agent.cordis.yml
  48. 1 1
      packages/preset/agent-presets/presets/ptc/agent.cordis.yml
  49. 1 1
      packages/preset/agent-presets/presets/standard/agent.cordis.yml
  50. 6 0
      pnpm-lock.yaml
  51. 1 1
      scripts/gen-cordis-catalog.ts
  52. 3 3
      scripts/gen-doc-graphs.ts
  53. 0 80
      snapshots/session/agent-instructions/tool-schemas.expected.json
  54. 0 40
      snapshots/session/background-confinement-failure/tool-schemas.expected.json
  55. 0 14
      snapshots/session/both-mode-turn/system-prompt.expected.md
  56. 0 40
      snapshots/session/both-mode-turn/tool-schemas.expected.json
  57. 0 40
      snapshots/session/browser-use-chrome-devtools-mcp/tool-schemas.expected.json
  58. 0 40
      snapshots/session/browser-use-playwright-mcp/tool-schemas.expected.json
  59. 0 40
      snapshots/session/browser-use-stagehand-native/tool-schemas.expected.json
  60. 0 80
      snapshots/session/compaction-recovery/tool-schemas.expected.json
  61. 0 40
      snapshots/session/computer-use-cua-driver-mcp/tool-schemas.expected.json
  62. 0 40
      snapshots/session/computer-use-cua-driver-native/tool-schemas.expected.json
  63. 0 14
      snapshots/session/cordis-inspect-jsdoc/system-prompt.expected.md
  64. 0 40
      snapshots/session/cordis-inspect-jsdoc/tool-schemas.expected.json
  65. 0 40
      snapshots/session/deepseek-messages-system-prompt/tool-schemas.expected.json
  66. 0 40
      snapshots/session/deepseek-protocol-system-prompt/tool-schemas.expected.json
  67. 0 40
      snapshots/session/foreground-confinement-timeout/tool-schemas.expected.json
  68. 0 40
      snapshots/session/fs-glob-sampling/tool-schemas.expected.json
  69. 0 40
      snapshots/session/lsp-definition/tool-schemas.expected.json
  70. 0 14
      snapshots/session/mcp-resources-ptc/system-prompt.expected.md
  71. 0 40
      snapshots/session/mcp-resources/tool-schemas.expected.json
  72. 3 0
      snapshots/session/plugin-manager/cordis.snapshot.yml
  73. 2 1
      snapshots/session/plugin-manager/cordis.yml
  74. 0 40
      snapshots/session/product-subagent-both/tool-schemas.expected.json
  75. 0 40
      snapshots/session/product-subagent-codex/tool-schemas.expected.json
  76. 0 40
      snapshots/session/product-subagent-result-diagnostic/tool-schemas.expected.json
  77. 0 40
      snapshots/session/provider-cwd/tool-schemas.expected.json
  78. 0 14
      snapshots/session/ptc-node-read-only/system-prompt.expected.md
  79. 0 14
      snapshots/session/ptc-node-workspace/system-prompt.expected.md
  80. 0 15
      snapshots/session/ptc-python-turn/system-prompt.expected.md
  81. 0 14
      snapshots/session/ptc-read-image/system-prompt.expected.md
  82. 0 14
      snapshots/session/ptc-turn/system-prompt.expected.md
  83. 0 40
      snapshots/session/pty-tools-sandbox-backend/tool-schemas.expected.json
  84. 0 40
      snapshots/session/ralph-loop/tool-schemas.1.expected.json
  85. 0 40
      snapshots/session/ralph-loop/tool-schemas.2.expected.json
  86. 0 40
      snapshots/session/ralph-loop/tool-schemas.expected.json
  87. 0 40
      snapshots/session/session-query-spill/tool-schemas.expected.json
  88. 0 40
      snapshots/session/subagent-acp-diagnostic/tool-schemas.expected.json
  89. 0 40
      snapshots/session/subagent-child-question-rejection/tool-schemas.expected.json
  90. 0 40
      snapshots/session/subagent-tool-filter/tool-schemas.1.expected.json
  91. 0 40
      snapshots/session/system-prompt-in-history/tool-schemas.expected.json
  92. 0 40
      snapshots/session/text-turn/tool-schemas.expected.json
  93. 0 40
      snapshots/session/web-fetch/tool-schemas.expected.json
  94. 0 40
      snapshots/session/workflow-confinement/tool-schemas.expected.json

+ 2 - 2
.agents/notes/implemented/architecture/2026-09-14-current-profile-plugin-management.i18n.yaml

@@ -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 .agents/notes/implemented/architecture/2026-09-14-current-profile-plugin-management.md
-2026-09-14-current-profile-plugin-management.md: 11f1dd1a269a1213dde27ce529276e1369564c21
-2026-09-14-current-profile-plugin-management.zh.md: 524f165215a0bd6a38f60062ad84c8fe22bcb993
+2026-09-14-current-profile-plugin-management.md: 316fb57a7826ae937f8f6225a2b2425563476bb1
+2026-09-14-current-profile-plugin-management.zh.md: c2b6a0c5420f8b028ff6e3db6839b09e4672a65a

+ 2 - 4
.agents/notes/implemented/architecture/2026-09-14-current-profile-plugin-management.md

@@ -10,13 +10,11 @@ Web and agent controls need to change a running profile without creating an inde
 
 ## Decision
 
-[Plugin Manager](../../../../packages/boot/plugin-manager/README.md) and `dsh plugin` call the same asynchronous package operations. The launcher supplies the current profile and resolution locations. CLI and service mutations hold the profile manifest's writer lock; [DSH HMR](../../../../packages/boot/hmr/README.md) serializes module replacement, Include refresh, profile recomposition and service mutations through one queue. The launcher registers the `hmr/before-reload` file-lock wrapper before plugin startup. Package mutations take that same file lock inside `hmr.runExclusive()`. Each generation re-reads the manifest, bundle layers and user patches while retaining invocation overlay precedence.
+[Plugin Manager](../../../../packages/boot/plugin-manager/README.md) and `dsh plugin` call the same asynchronous package operations. The launcher supplies `ctx.profileContext` as data: profile and resolution locations, startup bundles, reload policy and invocation overlays. Shared functions compose the current files; this interface contains no callbacks or mutation methods. CLI and service mutations hold the profile manifest's writer lock; [DSH HMR](../../../../packages/boot/hmr/README.md) serializes module replacement, Include refresh, profile recomposition and service mutations through one queue. The launcher registers the `hmr/before-reload` file-lock wrapper before plugin startup. Package mutations take that same file lock inside `hmr.runExclusive()`. Each generation re-reads the manifest, bundle layers and user patches while retaining invocation overlay precedence.
 
 Profile files remain the persisted state: entry toggles edit only `disabled` in the YAML document, and bundle toggles edit the ordered string list. Dependency updates do not reactivate retained disabled bundles. A service removal first applies the composition without the bundle and waits for old fibers to finish before deleting the dependency. Saved configuration, pnpm completion and runtime activation have separate outcomes; failure preserves the actual partial state and a diagnostic path.
 
-This extends the [profile bundle composition decision](2026-08-05-profile-plugin-bundles.md). Startup profiles keep their process composition, and Desktop package management remains shell-owned. Web controls and agent tools call the same service, whose batched durable notices inform live Agents without waking them.
-
-[Client HMR](../../../../packages/client/hmr/README.md) applies Host module-graph changes to the connected browser: removed entries drain their effect disposers before cached factories and styles are removed; added entries load through the existing Client module system. Plugin-owned aggregate interfaces follow their declared client entry, so disabling an internal Host child does not imply removing part of an aggregate client.
+This extends the [profile bundle composition decision](2026-08-05-profile-plugin-bundles.md). Startup profiles keep their process composition, and Desktop package management remains shell-owned. Web controls and explicitly enabled agent tools call the same service, whose batched durable notices inform live Agents without waking them. The agent tool is disabled by default in the base bundle and shipped presets.
 
 ## Alternatives considered
 

+ 2 - 4
.agents/notes/implemented/architecture/2026-09-14-current-profile-plugin-management.zh.md

@@ -10,13 +10,11 @@ Web 和 Agent 控件需要修改运行中的 profile,同时避免另建包安
 
 ## 决策
 
-[插件管理器](../../../../packages/boot/plugin-manager/README.zh.md)与 `dsh plugin` 调用同一套异步包操作。launcher 提供当前 profile 和解析位置。CLI 与 service 修改持有 profile manifest 的写锁;[DSH HMR](../../../../packages/boot/hmr/README.zh.md) 通过同一队列串行执行模块替换、Include 刷新、profile 重新组合与 service 修改。launcher 在插件启动前注册 `hmr/before-reload` 文件锁包装。包修改在 `hmr.runExclusive()` 内获取同一文件锁。每次重载重新读取 manifest、组合包层与用户 patch,同时保留调用级 overlay 的优先级。
+[插件管理器](../../../../packages/boot/plugin-manager/README.zh.md)与 `dsh plugin` 调用同一套异步包操作。launcher 通过纯数据 `ctx.profileContext` 提供 profile 与解析位置、启动时组合包、重载策略和调用级 overlay。共享函数组合当前文件;该接口不包含回调或修改方法。CLI 与 service 修改持有 profile manifest 的写锁;[DSH HMR](../../../../packages/boot/hmr/README.zh.md) 通过同一队列串行执行模块替换、Include 刷新、profile 重新组合与 service 修改。launcher 在插件启动前注册 `hmr/before-reload` 文件锁包装。包修改在 `hmr.runExclusive()` 内获取同一文件锁。每次重载重新读取 manifest、组合包层与用户 patch,同时保留调用级 overlay 的优先级。
 
 profile 文件保持为持久状态:条目开关只修改 YAML 文档中的 `disabled`,组合包开关修改有序字符串列表。更新依赖不会重新激活保留的已停用组合包。service 删除组合包时,先应用去掉该组合包的配置,等待旧 fiber 完成卸载后再删除依赖。已保存配置、pnpm 完成状态与运行时激活分别报告;失败保留实际的部分状态与诊断路径。
 
-这扩展了[profile 组合包决策](2026-08-05-profile-plugin-bundles.zh.md)。startup profile 保留进程组合,Desktop 包管理仍由 shell 持有。Web 控件与 Agent 工具调用同一 service;service 合并持久通知,告知存活 Agent 而不唤醒它们。
-
-[Client HMR](../../../../packages/client/hmr/README.zh.md) 将 Host 模块图变化应用到已连接的浏览器:移除条目时,先等待其 effect disposer 完成,再清除缓存工厂和样式;新增条目通过现有 Client 模块系统加载。插件提供的聚合界面跟随其声明的客户端条目,因此停用内部 Host 子插件不意味着移除聚合客户端的一部分。
+这扩展了[profile 组合包决策](2026-08-05-profile-plugin-bundles.zh.md)。startup profile 保留进程组合,Desktop 包管理仍由 shell 持有。Web 控件与显式启用的 Agent 工具调用同一 service;service 合并持久通知,告知存活 Agent 而不唤醒它们。base 组合包和内置预设默认禁用该 Agent 工具。
 
 ## 考虑过的替代方案
 

+ 11 - 58
apps/cli/src/profile-boot.ts

@@ -20,19 +20,17 @@ import { FiberState, type Context } from '@deepseek-ai/cordis'
 import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
 import {
   boot,
-  composeEntries,
+  readProfilePatches,
   healProfilesModuleFallback,
   initProfile,
   installFailLoud,
-  loadOptionalPatches,
   loadOverlayPatches,
   loadProfile,
   PROFILE_PATCH_FILENAME,
   PROFILE_TEMPLATES,
   resolveProfileDir,
-  loadProfileDirectory,
   reconcileProfilePatches,
-  type ProfileRuntime,
+  type ProfileContext,
   type Profile,
 } from '@deepseek-ai/dsh-app-boot'
 import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
@@ -80,9 +78,6 @@ export function homePatchPath(): string {
 /** Absolute path of this dsh installation's package.json (both anchors: src/ and lib/ sit one level under apps/cli). */
 export const INSTALL_ANCHOR = fileURLToPath(new URL('../package.json', import.meta.url))
 
-/** The session-telemetry row id the DSH_TELEMETRY_DISABLED switch targets. */
-const TELEMETRY_ROW_ID = 'session-telemetry-otel'
-
 /** The empty root entry list every profile tree patches over. */
 const PROFILE_ROOT_CONFIG = `# dsh profile root — an empty entry list. The tree is composed as patches:
 # each bundle in package.json's dsh.profile.bundles, then cordis.patch.yml, then any
@@ -155,23 +150,6 @@ export function initializeProfileFromDefault(
     throw error
   }
 }
-
-/**
- * Resolve the telemetry opt-out switch into its boot patch. ANY non-empty
- * value (including `'0'`/`'false'`) disables: a privacy switch prefers
- * off-by-mistake over on-by-mistake. A composition without the telemetry row
- * exports nothing, so the switch is then trivially satisfied and no patch is
- * generated — custom profiles need not mount telemetry to run with the
- * switch set.
- * @param disabledEnv - the raw `DSH_TELEMETRY_DISABLED` value (`undefined` when unset).
- * @param hasRow - whether the composition carries the telemetry row.
- * @returns the disable patch, or `undefined` when no hard-disable patch is required.
- */
-export function resolveTelemetryPatch(disabledEnv: string | undefined, hasRow: boolean): PatchOptions | undefined {
-  if ((disabledEnv ?? '') === '' || !hasRow) return undefined
-  return { id: TELEMETRY_ROW_ID, disabled: true }
-}
-
 /**
  * Load a resolved profile for `name` and (re)write the empty root config. The
  * root is always rewritten: the whole composition is patch layers, and the
@@ -304,42 +282,16 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
     }
   }))
   let lastInputs = readInputs()
-  const composeLive = (): PatchOptions[] => {
-    const profile = loadProfileDirectory(NAME, composed.profile.dir, INSTALL_ANCHOR)
-    const patches = structuredClone([
-      ...profile.layers.flatMap(layer => layer.patches),
-      ...profile.patches,
-      ...loadOptionalPatches(NAME, homePatchPath()) ?? [],
-      ...composed.overlays,
-    ])
-    const telemetryPatch = resolveTelemetryPatch(process.env.DSH_TELEMETRY_DISABLED,
-      composeEntries([patches]).some(row => row.id === TELEMETRY_ROW_ID))
-    if (telemetryPatch !== undefined) patches.push(telemetryPatch)
-    return patches
-  }
-  const runtime: ProfileRuntime = {
-    name: options.profile, dir: composed.profile.dir, installAnchor: INSTALL_ANCHOR,
+  const profileContext: ProfileContext = {
+    name: options.profile,
+    dir: composed.profile.dir, patchPath: composed.profile.patchPath, installAnchor: INSTALL_ANCHOR,
     startedBundles: composed.profile.layers.map(layer => layer.packageName),
     cwd: process.cwd(), home: resolveDshHome(), patchReload: composed.profile.patchReload,
-    read: () => loadProfileDirectory(NAME, composed.profile.dir, INSTALL_ANCHOR),
-    entries: () => composeEntries([composeLive()]),
-    mutate: (operation, waitMs) => {
-      const locked = () => withFileLock(manifestPath, operation, waitMs === undefined ? undefined : { waitMs })
-      const hmr = app.current?.get('hmr')
-      return hmr === undefined ? locked() : hmr.runExclusive(locked)
-    },
-    async reload() {
-      if (composed.profile.patchReload === 'startup') return
-      if (app.current === undefined) throw new Error('dsh: profile is not running')
-      lastInputs = readInputs()
-      await reconcileProfilePatches(app.current, composeLive(), NAME)
-    },
+    overlays: composed.overlays, telemetryDisabledEnv: process.env.DSH_TELEMETRY_DISABLED,
   }
-  // Cloned for the same insert-aliasing reason as composeLive: the boot
-  // application must not mutate the objects later reloads recompose from.
-  const ctx = await boot(NAME, rootConfig, composeLive(), (hostCtx) => {
+  const ctx = await boot(NAME, rootConfig, readProfilePatches(NAME, profileContext), (hostCtx) => {
     app.current = hostCtx
-    hostCtx.provide('profileRuntime', runtime)
+    hostCtx.provide('profileContext', profileContext)
     hostCtx.on('hmr/before-reload', next => withFileLock(manifestPath, next))
     // Before any config-tree entry mounts, so plugins resolve all launch-time
     // environment values from the same immutable launch snapshot.
@@ -378,7 +330,8 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
       }
       const refresh = async (): Promise<void> => {
         if (readInputs() === lastInputs) return
-        await runtime.reload()
+        lastInputs = readInputs()
+        await reconcileProfilePatches(ctx, readProfilePatches(NAME, profileContext), NAME)
       }
       const watching = ctx.inject(['hmr'], async (owner) => {
         for (const filename of watchedFiles) {
@@ -387,7 +340,7 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
       })
       await watching.await()
       // Include writes that finished while the file watchers were registering.
-      await runtime.mutate(refresh)
+      await ctx.hmr.runExclusive(() => withFileLock(manifestPath, refresh))
     } catch (error) {
       suppressShutdownError(ctx, signalShutdown.signal, error)
     }

+ 1 - 1
apps/cli/tests/telemetry-switch.spec.ts

@@ -1,5 +1,5 @@
 import { describe, expect, it } from 'vitest'
-import { resolveTelemetryPatch } from '../src/profile-boot.ts'
+import { resolveTelemetryPatch } from '@deepseek-ai/dsh-app-boot'
 
 describe('resolveTelemetryPatch', () => {
   it('preserves the configured telemetry mode when the hard-disable switch is unset or empty', () => {

+ 2 - 2
docs/capability-seams.i18n.yaml

@@ -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/capability-seams.md
-capability-seams.md: aa6e1e0fe7cb14441d27fc325647953c86d6936c
-capability-seams.zh.md: be961e9233ff3f22f98144174bb53efd93773a84
+capability-seams.md: 5db5d11195355330b3c0bf3f58c928bd3533cf85
+capability-seams.zh.md: bfcd60916f615f173bc167623462841e25eca2a2

+ 4 - 4
docs/capability-seams.md

@@ -13,7 +13,7 @@ flowchart LR
   pkg_plugin_manager["plugin-manager"]
   svc_pluginManager["ctx.pluginManager<br/>Current-profile plugin and bundle management"]
   pkg_ui_settings_plugin_inventory["ui-settings-plugin-inventory"]
-  svc_profileRuntime["ctx.profileRuntime<br/>Launcher-owned profile reload coordination"]
+  svc_profileContext["ctx.profileContext<br/>Launcher-owned profile data"]
   pkg_mcp_resources["mcp-resources"]
   svc_mcpResources["ctx.mcpResources<br/>Scoped MCP resource access"]
   pkg_mcp_client["mcp-client"]
@@ -260,7 +260,7 @@ flowchart LR
   pkg_api_workspace_controller --> svc_directoryPickerController
   pkg_api_workspace_controller --> svc_workspaceController
   pkg_api_workspace_files --> svc_workspaceFiles
-  pkg_app_boot --> svc_profileRuntime
+  pkg_app_boot --> svc_profileContext
   pkg_attachment --> svc_attachments
   pkg_attachment_local --> svc_attachments
   pkg_authorization --> svc_authorization
@@ -428,7 +428,7 @@ flowchart LR
   svc_mcpResources --> pkg_mcp_resources
   svc_pluginManager --> pkg_plugin_manager
   svc_pluginManager --> pkg_ui_settings_plugin_inventory
-  svc_profileRuntime --> pkg_plugin_manager
+  svc_profileContext --> pkg_plugin_manager
   svc_ptcRuntime --> pkg_tools
   svc_ptcRuntime --> pkg_workflow_ptc
   svc_sandbox --> pkg_bash_sandbox
@@ -524,7 +524,7 @@ flowchart LR
 | --- | --- | --- | --- | --- | --- | --- |
 | `ctx.hmr` | `core` | [`hmr`](../packages/boot/hmr) | - | [`app-boot`](../packages/boot/app-boot) | - | Owns module and exact configuration watchers; application mutations share its queue and automatic reloads await the application file lock. |
 | `ctx.pluginManager` | `core` | [`plugin-manager`](../packages/boot/plugin-manager) | - | [`plugin-manager`](../packages/boot/plugin-manager), `ui-settings-plugin-inventory` | - | Shares profile package operations with the CLI and reports persisted and running state to Web and agent callers. |
-| `ctx.profileRuntime` | `core` | [`app-boot`](../packages/boot/app-boot) | - | [`plugin-manager`](../packages/boot/plugin-manager) | - | The dsh launcher supplies profile locations and composition; reload scheduling belongs to dsh-hmr. |
+| `ctx.profileContext` | `core` | [`app-boot`](../packages/boot/app-boot) | - | [`plugin-manager`](../packages/boot/plugin-manager) | - | The dsh launcher supplies data-only profile locations and composition inputs; reload scheduling belongs to dsh-hmr. |
 | `ctx.mcpResources` | `seam` | [`mcp-resources`](../packages/mcp/mcp-resources) | [`mcp-client`](../packages/mcp/mcp-client) | [`mcp-resources`](../packages/mcp/mcp-resources) | - | Connection-owned providers serve shared resource tools in the calling agent scope. |
 | `ctx.browserUse` | `seam` | [`browser-use`](../packages/browser-use/browser-use) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | - | One provider-owned name per service instance. Providers own their tools and browser resources per live Session; the shared service has no browser operation API. |
 | `ctx.computerUse` | `seam` | [`computer-use`](../packages/computer-use/computer-use) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | - | One provider-owned name per service instance. Each provider also owns its model tools; the service has no common action API, runtime selection, or Session workflow lock. |

+ 4 - 4
docs/capability-seams.zh.md

@@ -15,7 +15,7 @@ flowchart LR
   pkg_plugin_manager["plugin-manager"]
   svc_pluginManager["ctx.pluginManager<br/>Current-profile plugin and bundle management"]
   pkg_ui_settings_plugin_inventory["ui-settings-plugin-inventory"]
-  svc_profileRuntime["ctx.profileRuntime<br/>Launcher-owned profile reload coordination"]
+  svc_profileContext["ctx.profileContext<br/>Launcher-owned profile data"]
   pkg_mcp_resources["mcp-resources"]
   svc_mcpResources["ctx.mcpResources<br/>Scoped MCP resource access"]
   pkg_mcp_client["mcp-client"]
@@ -262,7 +262,7 @@ flowchart LR
   pkg_api_workspace_controller --> svc_directoryPickerController
   pkg_api_workspace_controller --> svc_workspaceController
   pkg_api_workspace_files --> svc_workspaceFiles
-  pkg_app_boot --> svc_profileRuntime
+  pkg_app_boot --> svc_profileContext
   pkg_attachment --> svc_attachments
   pkg_attachment_local --> svc_attachments
   pkg_authorization --> svc_authorization
@@ -430,7 +430,7 @@ flowchart LR
   svc_mcpResources --> pkg_mcp_resources
   svc_pluginManager --> pkg_plugin_manager
   svc_pluginManager --> pkg_ui_settings_plugin_inventory
-  svc_profileRuntime --> pkg_plugin_manager
+  svc_profileContext --> pkg_plugin_manager
   svc_ptcRuntime --> pkg_tools
   svc_ptcRuntime --> pkg_workflow_ptc
   svc_sandbox --> pkg_bash_sandbox
@@ -526,7 +526,7 @@ flowchart LR
 | --- | --- | --- | --- | --- | --- | --- |
 | `ctx.hmr` | `core` | [`hmr`](../packages/boot/hmr) | - | [`app-boot`](../packages/boot/app-boot) | - | 负责模块和精确配置监听;应用修改共用其队列,自动重载等待应用文件锁。 |
 | `ctx.pluginManager` | `core` | [`plugin-manager`](../packages/boot/plugin-manager) | - | [`plugin-manager`](../packages/boot/plugin-manager), `ui-settings-plugin-inventory` | - | 与 CLI 共享 profile 包操作,并向 Web 和 Agent 调用方分别报告持久状态与运行状态。 |
-| `ctx.profileRuntime` | `core` | [`app-boot`](../packages/boot/app-boot) | - | [`plugin-manager`](../packages/boot/plugin-manager) | - | dsh launcher 提供 profile 位置和组合逻辑;重载调度由 dsh-hmr 负责。 |
+| `ctx.profileContext` | `core` | [`app-boot`](../packages/boot/app-boot) | - | [`plugin-manager`](../packages/boot/plugin-manager) | - | dsh launcher 提供纯数据形式的 profile 位置与组合输入;重载调度由 dsh-hmr 负责。 |
 | `ctx.mcpResources` | `seam` | [`mcp-resources`](../packages/mcp/mcp-resources) | [`mcp-client`](../packages/mcp/mcp-client) | [`mcp-resources`](../packages/mcp/mcp-resources) | - | 连接所有者提供的操作在调用 agent 的作用域内服务于共享资源工具。 |
 | `ctx.browserUse` | `seam` | [`browser-use`](../packages/browser-use/browser-use) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | - | 每个服务实例注册一个提供方拥有的名称。提供方按实时 Session 拥有自己的工具与浏览器资源;共享服务不提供浏览器操作 API。 |
 | `ctx.computerUse` | `seam` | [`computer-use`](../packages/computer-use/computer-use) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | - | 每个服务实例只注册一个提供方自定的名称。各提供方也拥有自己的模型工具;服务不提供通用操作 API、运行时选择或 Session 流程锁。 |

+ 2 - 2
docs/config-catalog.i18n.yaml

@@ -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/config-catalog.md
-config-catalog.md: b71b3754280d8e0afd4352cec906b1826e8cdad5
-config-catalog.zh.md: 51afc49c5f03d30627767facf3987f612528da59
+config-catalog.md: ea8fc7a3805454a0e28755228a38d298ad54ee8f
+config-catalog.zh.md: 022914e440521598238ef8c9da441b27dc5b7733

+ 2 - 2
docs/config-catalog.md

@@ -1801,7 +1801,7 @@ Source: [`packages/plan/plan-mode/src/index.ts:64`](../packages/plan/plan-mode/s
 
 ## `@deepseek-ai/dsh-plugin-manager`
 
-Requires: `loader` · `profileRuntime`
+Requires: `loader` · `profileContext`
 
 ```ts config-catalog
 /** Limits for package diagnostics and change notifications. */
@@ -1815,7 +1815,7 @@ export interface Config {
 }
 ```
 
-Source: [`packages/boot/plugin-manager/src/index.ts:19`](../packages/boot/plugin-manager/src/index.ts)
+Source: [`packages/boot/plugin-manager/src/index.ts:21`](../packages/boot/plugin-manager/src/index.ts)
 
 <a id="deepseek-aidsh-plugin-package-inventory-deepseek"></a>
 

+ 1 - 1
docs/config-catalog.zh.md

@@ -1803,7 +1803,7 @@ export interface PlanModeConfig {
 
 ## `@deepseek-ai/dsh-plugin-manager`
 
-依赖: `loader` · `profileRuntime`
+依赖: `loader` · `profileContext`
 
 ```ts config-catalog
 /** Limits for package diagnostics and change notifications. */

+ 2 - 2
docs/subsystems/boot.i18n.yaml

@@ -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/boot.md
-boot.md: 09d0dc5a70ea0fcb1e269e4621768cf489ba9a20
-boot.zh.md: 1bc74a5b46afc8d4e10924822a9cc65ae0fa8d57
+boot.md: 63240fdb2001095a475ed13e270340527a485d08
+boot.zh.md: 509d41cc4de46d64b9ebab4a23a8f9a09cd5551d

+ 4 - 26
docs/subsystems/boot.md

@@ -100,35 +100,13 @@ Manage profile files and apply their declared reload lifecycle.
 
 Source: [`packages/boot/plugin-manager/src/index.ts`](../../packages/boot/plugin-manager/src/index.ts)
 
-<a id="ctxprofileruntime--profileruntime"></a>
+<a id="ctxprofilecontext--profilecontext"></a>
 
-### `ctx.profileRuntime` — `ProfileRuntime`
+### `ctx.profileContext` — `ProfileContext`
 
-Current-process profile operations; callbacks run under the shared profile write lock.
+Current profile facts; scheduling and mutation belong to their callers.
 
-```ts cordis-catalog
-/** Read the current manifest and bundle patch layers without initializing a profile.
- * @returns Resolved disk configuration.
- */
-read(): Profile
-
-/** Compose disk configuration with the invocation's higher-priority layers.
- * @returns Effective entry options in composition order.
- */
-entries(): EntryOptions[]
-
-/** Serialize a mutation with file watching and other profile writers.
- * @param operation Work performed while holding the profile manifest lock.
- * @param waitMs Maximum lock acquisition time; omission uses the file writer default.
- * @returns The operation's result.
- */
-mutate<T>(operation: () => Promise<T>, waitMs?: number): Promise<T>
-
-/** Apply the current disk configuration; call only inside mutate. */
-reload(): Promise<void>
-```
-
-Source: [`packages/boot/app-boot/src/profile-runtime.ts`](../../packages/boot/app-boot/src/profile-runtime.ts)
+Source: [`packages/boot/app-boot/src/profile-context.ts`](../../packages/boot/app-boot/src/profile-context.ts)
 
 <a id="hmr-events"></a>
 

+ 4 - 26
docs/subsystems/boot.zh.md

@@ -100,35 +100,13 @@ Manage profile files and apply their declared reload lifecycle.
 
 Source: [`packages/boot/plugin-manager/src/index.ts`](../../packages/boot/plugin-manager/src/index.ts)
 
-<a id="ctxprofileruntime--profileruntime"></a>
+<a id="ctxprofilecontext--profilecontext"></a>
 
-### `ctx.profileRuntime` — `ProfileRuntime`
+### `ctx.profileContext` — `ProfileContext`
 
-Current-process profile operations; callbacks run under the shared profile write lock.
+Current profile facts; scheduling and mutation belong to their callers.
 
-```ts cordis-catalog
-/** Read the current manifest and bundle patch layers without initializing a profile.
- * @returns Resolved disk configuration.
- */
-read(): Profile
-
-/** Compose disk configuration with the invocation's higher-priority layers.
- * @returns Effective entry options in composition order.
- */
-entries(): EntryOptions[]
-
-/** Serialize a mutation with file watching and other profile writers.
- * @param operation Work performed while holding the profile manifest lock.
- * @param waitMs Maximum lock acquisition time; omission uses the file writer default.
- * @returns The operation's result.
- */
-mutate<T>(operation: () => Promise<T>, waitMs?: number): Promise<T>
-
-/** Apply the current disk configuration; call only inside mutate. */
-reload(): Promise<void>
-```
-
-Source: [`packages/boot/app-boot/src/profile-runtime.ts`](../../packages/boot/app-boot/src/profile-runtime.ts)
+Source: [`packages/boot/app-boot/src/profile-context.ts`](../../packages/boot/app-boot/src/profile-context.ts)
 
 <a id="hmr-events"></a>
 

+ 2 - 2
docs/subsystems/client-modules.i18n.yaml

@@ -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/client-modules.md
-client-modules.md: eec15c30a88b0d3e73225bcb4a20b31b781269d1
-client-modules.zh.md: 0befa113f0e394b9d135840b96d62307751695ee
+client-modules.md: 12925fa8ddb7c7193af76cf1bed5894f5d7d75e0
+client-modules.zh.md: 513391af8369a5ba4d9a439a14f7fb5eb1f45d27

+ 1 - 1
docs/subsystems/client-modules.md

@@ -100,7 +100,7 @@ interface ClientArtifactBaseline {
 
 `ClientModuleRegistry` (`ctx.clientModules`, defined in [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts)) exposes reads and the rebuild face; signatures are in the generated [service catalog](#ctxclientmodules--clientmoduleregistry). `graph()` returns the current composed graph (a stable object between changes), `clientPath(id)` returns the bundle's absolute path, and `artifactBaseline(id)` returns the bundle stat values captured before the current snapshot was read. `rebuilt(id)` is the only entry point through which changed bundle content reaches the graph: it re-hashes the bundle together with its current source map, and only a real rev change recomposes the graph and notifies. `onRebuilt` fires per changed bundle with the new rev; `onGraphChanged` fires after any flush that recomposed the graph (row added or removed, or a rebuilt rev change) and is pull-model — listeners re-read `graph()`. Both notification paths contain listener exceptions so one throwing subscriber cannot skip later subscribers or kill whatever triggered the flush.
 
-The Web profile mounts [dsh-client-hmr](../../packages/client/hmr/README.md) to broadcast graph membership and rebuilt revisions through SSE. The browser adds and removes Loader entries from validated graphs; bundle stat polling supplies development rebuild notifications. The module host itself does not watch files.
+In development, [dsh-client-hmr](../../packages/client/hmr/README.md) is the registry's watch driver: its node half stat-polls every graph row's bundle from the module host's pre-read baseline, calls `rebuilt(id)` only for a changed or dirty row, resyncs its watch set through `onGraphChanged`, and broadcasts rev changes to the browser half over SSE. Source-map changes alone do not trigger a reload; the current map joins the snapshot when a bundle change does. Production graphs omit the HMR row entirely; the module host itself never watches files.
 
 <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
 

+ 1 - 1
docs/subsystems/client-modules.zh.md

@@ -100,7 +100,7 @@ interface ClientArtifactBaseline {
 
 `ClientModuleRegistry`(`ctx.clientModules`,定义于 [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts))暴露读取面与重建面;签名见生成的[服务目录](#ctxclientmodules--clientmoduleregistry)。`graph()` 返回当前组合出的图(两次变更之间是同一个稳定对象),`clientPath(id)` 返回 bundle 的绝对路径,`artifactBaseline(id)` 返回读取当前快照前捕获的 bundle stat 值。`rebuilt(id)` 是变化后的 bundle 内容到达图的唯一入口:它把 bundle 与当前 source map 一起重新哈希,只有 rev 真正变化才会重新组合图并发出通知。`onRebuilt` 按发生变化的 bundle 逐个触发并携带新 rev;`onGraphChanged` 在任何一次重新组合了图的 flush 之后触发(行的增删,或 rebuilt 带来的 rev 变化),并采用拉取模型——监听器自行重读 `graph()`。两条通知路径都会兜住监听器异常,因此一个抛错的订阅者既不能让后续订阅者被跳过,也不能杀死触发这次 flush 的一方。
 
-Web profile 加载 [dsh-client-hmr](../../packages/client/hmr/README.zh.md),通过 SSE 广播模块图成员变化和重建 revision。浏览器根据校验后的模块图增删 Loader 条目;bundle stat 轮询提供开发构建通知。模块宿主自身不监听文件。
+开发环境下,[dsh-client-hmr](../../packages/client/hmr/README.zh.md) 是注册表的监视驱动:它的 Node 半从 module host 读文件前记录的基线出发,对图中每一行的 bundle 做 stat 轮询,只为变化或标脏的 row 调用 `rebuilt(id)`,经 `onGraphChanged` 重新同步监视集合,并通过 SSE(Server-Sent Events)把 rev 变化广播给浏览器半。仅 source map 变化不会触发重载;bundle 变化时,当前 map 会一起进入快照。生产环境的图完全不含 HMR(热模块替换)行;module host 自身从不监视文件。
 
 <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
 

+ 2 - 2
packages/boot/app-boot/README.i18n.yaml

@@ -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/boot/app-boot/README.md
-README.md: 9162384bdacb11250a286f12676e044e36be73a3
-README.zh.md: 832b44749fa98fe328e49bae2edfb8f7f4e04574
+README.md: 0d776c5deb4a7158e795cc3341b79935ec0f06d6
+README.zh.md: 042d4f68e52e865961804e6262cf4f31ce7a09b9

+ 1 - 0
packages/boot/app-boot/README.md

@@ -103,6 +103,7 @@ This section explains how the outcomes above are realized and points at the code
 
 ### Design notes
 
+- **Profile launch data.** `ctx.profileContext` contains only profile locations, reload policy, startup bundle names, parsed invocation overlays and the telemetry opt-out value. `readProfilePatches()` reads and composes current files from those inputs; callers schedule and apply the result.
 - **Channel-neutral library.** The package carries no loader hooks and no dev-mode surface; the [`dsh` app](../../../apps/cli/README.md) owns its Node source-launch hook and consumes these helpers for the boot sequence, and built consumers use plain Node package resolution.
 - **Two Loader builtins.** `mountRootInclude` registers `cordis:include` and `cordis:group` as Loader builtins: a group row gives one `isolate` realm to a provider and its consumers together, and an agent preset outside this workspace cannot resolve `@deepseek-ai/cordis-plugin-group` by name. Both load through the ambient module pipeline rather than the included tree's own specifier resolution.
 - **Consumer-owned strictness.** Ordinary Loader groups keep successful siblings. App-boot applies the global required-entry policy after initial settlement; agent presets and dynamic multi-entry compositions own and dispose their separate generation when they require all-or-nothing setup. App-boot reads failed fibers to report their recorded errors and coalesces duplicate Loader rejection notifications through one process checkpoint.

+ 1 - 0
packages/boot/app-boot/README.zh.md

@@ -103,6 +103,7 @@ Loader 结算后,app-boot 将 optional 失败报告为警告;若已启用的
 
 ### 设计说明
 
+- **Profile 启动数据。** `ctx.profileContext` 只包含 profile 位置、重载策略、启动时组合包名称、已解析的调用级 overlay 与遥测退出值。`readProfilePatches()` 根据这些输入读取并组合当前文件;调用方负责调度和应用结果。
 - **与渠道无关的库。** 此包不包含 loader 钩子,也不提供开发模式接口;[`dsh` 应用](../../../apps/cli/README.zh.md) 持有自己的 Node 源码启动钩子,并在启动序列中使用这些 helper,构建后的消费方则使用普通 Node 包解析。
 - **两个 Loader builtin。** `mountRootInclude` 把 `cordis:include` 与 `cordis:group` 注册为 Loader builtin:group 行能把一个提供方与它的消费方放进同一个 `isolate` realm,而位于本工作区之外的 agent preset 无法按名称解析 `@deepseek-ai/cordis-plugin-group`。两者都通过宿主的模块管线加载,而非被包含树自身的说明符解析。
 - **由 consumer 持有严格语义。** 普通 Loader group 保留成功 sibling。App-boot 在首次结算后应用全局 required-entry policy;agent preset 与动态多 entry 组合在需要 all-or-nothing setup 时,持有并拆卸各自的独立 generation。App-boot 读取 failed fiber 来报告已记录的错误,并在一个进程检查点内合并 Loader 重复的 rejection 通知。

+ 1 - 1
packages/boot/app-boot/src/index.ts

@@ -18,7 +18,7 @@ import Group from '@deepseek-ai/cordis-plugin-group'
 import { dshHomePath, resolveDshHome } from '@deepseek-ai/dsh-home-paths'
 import { createLaunchEnvironmentSnapshot, type LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
 import type {} from '@deepseek-ai/dsh-hmr'
-export type { ProfileRuntime } from './profile-runtime.ts'
+export { readProfilePatches, resolveTelemetryPatch, type ProfileContext } from './profile-context.ts'
 import type {} from '@deepseek-ai/dsh-system-prompt'
 
 declare module '@deepseek-ai/cordis' {

+ 66 - 0
packages/boot/app-boot/src/profile-context.ts

@@ -0,0 +1,66 @@
+/** Launcher-owned profile locations and composition inputs. */
+import { join } from 'node:path'
+import { composeEntries, loadProfileDirectory, PROFILE_PATCH_FILENAME } from './profile.ts'
+import { loadOptionalPatches } from './index.ts'
+import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
+
+/** Current profile facts; scheduling and mutation belong to their callers. */
+export interface ProfileContext {
+  readonly name: string
+  readonly dir: string
+  readonly patchPath: string
+  readonly installAnchor: string
+  readonly cwd: string
+  readonly home: string
+  /** Bundle packages used to start this process, before any persisted edits. */
+  readonly startedBundles: readonly string[]
+  readonly patchReload: 'live' | 'startup'
+  /** Parsed command-line overlays, applied above profile and home patches. */
+  readonly overlays: readonly PatchOptions[]
+  /** Launch-time DSH_TELEMETRY_DISABLED value; any non-empty value opts out. */
+  readonly telemetryDisabledEnv: string | undefined
+}
+
+declare module '@deepseek-ai/cordis' {
+  interface Context {
+    /** Present only in a profile launched by dsh. */
+    profileContext: ProfileContext
+  }
+}
+
+const TELEMETRY_ROW_ID = 'session-telemetry-otel'
+
+/**
+ * Resolve the telemetry opt-out switch into its boot patch. ANY non-empty
+ * value (including `'0'`/`'false'`) disables: a privacy switch prefers
+ * off-by-mistake over on-by-mistake. A composition without the telemetry row
+ * exports nothing, so the switch is then trivially satisfied and no patch is
+ * generated — custom profiles need not mount telemetry to run with the
+ * switch set.
+ * @param disabledEnv - the raw `DSH_TELEMETRY_DISABLED` value (`undefined` when unset).
+ * @param hasRow - whether the composition carries the telemetry row.
+ * @returns the disable patch, or `undefined` when no hard-disable patch is required.
+ */
+export function resolveTelemetryPatch(disabledEnv: string | undefined, hasRow: boolean): PatchOptions | undefined {
+  if ((disabledEnv ?? '') === '' || !hasRow) return undefined
+  return { id: TELEMETRY_ROW_ID, disabled: true }
+}
+
+/** Read current bundle and user layers with the launch-time overlays.
+ * @param binName Diagnostic prefix for malformed or missing configuration.
+ * @param context Data supplied by the profile launcher.
+ * @returns Detached ordered patches; this function does not update the Loader.
+ */
+export function readProfilePatches(binName: string, context: ProfileContext): PatchOptions[] {
+  const profile = loadProfileDirectory(binName, context.dir, context.installAnchor)
+  const patches = structuredClone([
+    ...profile.layers.flatMap(layer => layer.patches),
+    ...profile.patches,
+    ...(loadOptionalPatches(binName, join(context.home, PROFILE_PATCH_FILENAME)) ?? []),
+    ...context.overlays,
+  ])
+  const telemetryPatch = resolveTelemetryPatch(context.telemetryDisabledEnv,
+    composeEntries([patches]).some(row => row.id === TELEMETRY_ROW_ID))
+  if (telemetryPatch !== undefined) patches.push(telemetryPatch)
+  return patches
+}

+ 0 - 38
packages/boot/app-boot/src/profile-runtime.ts

@@ -1,38 +0,0 @@
-/** Launcher-provided access to the current profile's serialized configuration lifecycle. */
-import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
-import type { Profile } from './profile.ts'
-
-/** Current-process profile operations; callbacks run under the shared profile write lock. */
-export interface ProfileRuntime {
-  readonly name: string
-  readonly dir: string
-  readonly installAnchor: string
-  readonly cwd: string
-  readonly home: string
-  /** Bundle packages used to start this process, before any persisted edits. */
-  readonly startedBundles: readonly string[]
-  readonly patchReload: 'live' | 'startup'
-  /** Read the current manifest and bundle patch layers without initializing a profile.
-   * @returns Resolved disk configuration.
-   */
-  read(): Profile
-  /** Compose disk configuration with the invocation's higher-priority layers.
-   * @returns Effective entry options in composition order.
-   */
-  entries(): EntryOptions[]
-  /** Serialize a mutation with file watching and other profile writers.
-   * @param operation Work performed while holding the profile manifest lock.
-   * @param waitMs Maximum lock acquisition time; omission uses the file writer default.
-   * @returns The operation's result.
-   */
-  mutate<T>(operation: () => Promise<T>, waitMs?: number): Promise<T>
-  /** Apply the current disk configuration; call only inside mutate. */
-  reload(): Promise<void>
-}
-
-declare module '@deepseek-ai/cordis' {
-  interface Context {
-    /** Present only in a profile launched by dsh. */
-    profileRuntime: ProfileRuntime
-  }
-}

+ 24 - 0
packages/boot/app-boot/tests/profile.spec.ts

@@ -21,6 +21,7 @@ import {
   PROFILE_PATCH_FILENAME,
   PROFILE_TEMPLATES,
   readProfileManifest,
+  readProfilePatches,
   resolveBundleDir,
   resolveProfileDir,
   writeProfileManifest,
@@ -99,6 +100,29 @@ describe('resolveProfileDir', () => {
   })
 })
 
+it('composes current files from profile data and retains launch overlay and telemetry precedence', () => {
+  const home = tmp()
+  const installAnchor = stageInstallation({ base: { patch: '- insert:\n  - id: session-telemetry-otel\n    name: telemetry\n' } })
+  const dir = resolveProfileDir('test', home)
+  initProfile(dir, ['base'])
+  const patchPath = join(dir, PROFILE_PATCH_FILENAME)
+  writeFileSync(patchPath, '- id: session-telemetry-otel\n  disabled: true\n')
+  writeFileSync(join(home, PROFILE_PATCH_FILENAME), '- id: session-telemetry-otel\n  disabled: false\n')
+  const context = {
+    name: 'test', dir, patchPath, installAnchor, home, cwd: home,
+    startedBundles: ['base'], patchReload: 'live' as const,
+    overlays: [{ id: 'session-telemetry-otel', disabled: false }], telemetryDisabledEnv: 'false',
+  }
+  expect(composeEntries([readProfilePatches('test', context)])[0]?.disabled).toBe(true)
+  const enabled = { ...context, telemetryDisabledEnv: undefined }
+  expect(composeEntries([readProfilePatches('test', enabled)])[0]?.disabled).toBe(false)
+  const patches = readProfilePatches('test', enabled)
+  patches.at(-1)!.disabled = true
+  expect(context.overlays[0]?.disabled).toBe(false)
+  writeFileSync(join(home, PROFILE_PATCH_FILENAME), '- id: session-telemetry-otel\n  disabled: true\n')
+  expect(composeEntries([readProfilePatches('test', { ...enabled, overlays: [] })])[0]?.disabled).toBe(true)
+})
+
 describe('initProfile', () => {
   it('creates manifest, user patch layer, and pnpm workspace once, never overwriting', () => {
     const home = tmp()

+ 2 - 2
packages/boot/plugin-manager/README.i18n.yaml

@@ -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/boot/plugin-manager/README.md
-README.md: e2808920b211c020c62efa1265e96127bd5f5047
-README.zh.md: d330f3cdbbf7dff55e25d363c017224af663d017
+README.md: 36f470902d011b21b146600218a2a3258b025873
+README.zh.md: 0d63084eee76cd2d15e60e382e738a80504022bb

+ 9 - 1
packages/boot/plugin-manager/README.md

@@ -25,7 +25,14 @@ Manage the current profile's plugins without editing configuration by hand. Enab
 <a id="use-this-package"></a>
 ## Use this package
 
-Base-backed profiles provide the manager. In Web Settings, open Plugins and select Plugin list to manage bundles and uniquely addressable global plugin entries. Agent-preset rows remain read-only. The `plugin_manager` tool exposes the same operations.
+Base-backed profiles provide the manager. In Web Settings, open Plugins and select Plugin list to manage bundles and uniquely addressable global plugin entries. Agent-preset rows remain read-only. The `plugin_manager` tool exposes the same operations and is disabled by default.
+
+Enable the tool explicitly in the profile patch; agents using a preset also need its `tool-plugin-manager` entry enabled.
+
+```yaml
+- id: tool-plugin-manager
+  disabled: false
+```
 
 A plugin toggle writes only its `disabled` override in the profile's `cordis.patch.yml`. A bundle toggle changes `package.json`'s ordered `dsh.profile.bundles` list. Disabling retains the dependency; enabling appends the bundle at the end, which can change configuration precedence. Installation enables a new bundle by default. Home and invocation patches retain their higher priority.
 
@@ -99,6 +106,7 @@ Notices append context; they do not rewrite earlier messages.
 - Startup-only profiles cannot remove packages used to start the current process; stop it and use `dsh plugin`.
 - The manager cannot disable its own management components, change another profile, or edit an agent preset's composition.
 - Package failures may leave dependencies partially changed. Diagnostic logs remain under the profile's `.plugin-manager/logs` directory.
+- Browser bundle changes require a page refresh to load the current Client module graph. Management results describe Host activation.
 - Desktop package operations remain owned by the Desktop shell.
 
 <a id="dev-note"></a>

+ 9 - 1
packages/boot/plugin-manager/README.zh.md

@@ -25,7 +25,14 @@ kind: "package-reference"
 <a id="use-this-package"></a>
 ## 使用本包
 
-基于 base 的 profile 提供管理服务。在 Web 设置中打开插件并选择插件列表,即可管理组合包和能唯一定位的全局插件条目。Agent 预设条目保持只读。`plugin_manager` 工具提供相同操作。
+基于 base 的 profile 提供管理服务。在 Web 设置中打开插件并选择插件列表,即可管理组合包和能唯一定位的全局插件条目。Agent 预设条目保持只读。`plugin_manager` 工具提供相同操作,默认禁用。
+
+在 profile patch 中显式启用工具;使用预设的 Agent 还需要启用该预设中的 `tool-plugin-manager` 条目。
+
+```yaml
+- id: tool-plugin-manager
+  disabled: false
+```
 
 插件开关只写入 profile 的 `cordis.patch.yml` 中的 `disabled` 覆盖项。组合包开关修改 `package.json` 的有序 `dsh.profile.bundles` 列表。关闭保留依赖;开启追加到列表末尾,可能改变配置优先级。安装新组合包默认启用。home 和单次启动 patch 保留更高优先级。
 
@@ -99,6 +106,7 @@ kind: "package-reference"
 - 仅启动时加载的 profile 不能删除当前进程启动时使用的包;停止进程后使用 `dsh plugin`。
 - 管理器不能关闭自身所需的管理组件、修改其他 profile 或编辑 agent 预设组合。
 - 包管理失败可能留下部分依赖改动。诊断日志保留在 profile 的 `.plugin-manager/logs` 目录中。
+- 浏览器组合包变化需要刷新页面后才能加载当前 Client 模块图。管理结果描述 Host 激活状态。
 - Desktop 包管理操作仍由 Desktop shell 负责。
 
 <a id="dev-note"></a>

+ 8 - 2
packages/boot/plugin-manager/package.json

@@ -68,11 +68,15 @@
     "@deepseek-ai/dsh-llm": "workspace:^",
     "@deepseek-ai/dsh-subprocess": "workspace:^",
     "@deepseek-ai/dsh-typert-protocol": "workspace:^",
-    "@deepseek-ai/dsh-tools": "workspace:^"
+    "@deepseek-ai/dsh-tools": "workspace:^",
+    "@deepseek-ai/dsh-hmr": "workspace:^"
   },
   "peerDependenciesMeta": {
     "@deepseek-ai/dsh-agent": {
       "optional": true
+    },
+    "@deepseek-ai/dsh-hmr": {
+      "optional": true
     }
   },
   "devDependencies": {
@@ -85,6 +89,8 @@
     "@deepseek-ai/dsh-typert-protocol": "workspace:^",
     "@deepseek-ai/dsh-tools": "workspace:^",
     "@deepseek-ai/dsh-system-prompt": "workspace:^",
-    "@deepseek-ai/dsh-session": "workspace:^"
+    "@deepseek-ai/dsh-session": "workspace:^",
+    "@deepseek-ai/dsh-hmr": "workspace:^",
+    "@deepseek-ai/cordis-plugin-timer": "workspace:^"
   }
 }

+ 41 - 34
packages/boot/plugin-manager/src/index.ts

@@ -1,13 +1,15 @@
 /** Current-profile plugin and bundle management over shared dsh plugin operations. */
 import { readFileSync } from 'node:fs'
 import { join } from 'node:path'
+import { withFileLock } from '@deepseek-ai/dsh-atomic-write'
 import { Context } from '@deepseek-ai/cordis'
 import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
 import z from '@deepseek-ai/schemastery'
 import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol'
 import { readPluginInventory } from '@deepseek-ai/dsh-host-plugin-inventory'
-import { readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries } from '@deepseek-ai/dsh-app-boot'
-import type { ProfileRuntime } from '@deepseek-ai/dsh-app-boot'
+import { readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries, reconcileProfilePatches, readProfilePatches } from '@deepseek-ai/dsh-app-boot'
+import type {} from '@deepseek-ai/dsh-hmr'
+import type { ProfileContext } from '@deepseek-ai/dsh-app-boot'
 import type { Agent } from '@deepseek-ai/dsh-agent'
 import { createUserMessage } from '@deepseek-ai/dsh-llm'
 import { bundleManifest, runProfilePnpm, saveManifest } from './operations.ts'
@@ -32,9 +34,7 @@ const protectedModules = new Set([
   '@deepseek-ai/dsh-client-ui-settings-plugin-inventory',
   '@deepseek-ai/cordis-plugin-timer', '@deepseek-ai/dsh-client-connection',
   '@deepseek-ai/dsh-host-frontend-static', '@deepseek-ai/dsh-tools',
-  '@deepseek-ai/dsh-plugin-manager/tools',
   '@deepseek-ai/dsh-hmr',
-  '@deepseek-ai/dsh-client-hmr',
 ])
 
 /** Flatten only the groups addressable by the profile's patch composer. */
@@ -54,7 +54,7 @@ declare module '@deepseek-ai/cordis' {
 
 /** Manage profile files and apply their declared reload lifecycle. */
 export class PluginManager extends TypertRemoteService {
-  static inject = ['loader', 'profileRuntime']
+  static inject = ['loader', 'profileContext']
   static Config: z<Config> = z.object({
     outputBytes: z.number().step(1).min(1).default(16384),
     notificationDelayMs: z.number().step(1).min(0).default(250),
@@ -62,7 +62,7 @@ export class PluginManager extends TypertRemoteService {
   })
   private readonly ownerEntryId: string | undefined
   private readonly packageOperations = new Set<Promise<PackageResult>>()
-  private readonly runtime: ProfileRuntime
+  private readonly profile: ProfileContext
   private readonly outputBytes: number
   private readonly notificationDelayMs: number
   private readonly lockWaitMs: number
@@ -78,7 +78,7 @@ export class PluginManager extends TypertRemoteService {
     super(ctx, 'pluginManager')
     this.ownerEntryId = ctx.fiber.entry?.id
     this.ownerContext = ctx
-    this.runtime = ctx.profileRuntime
+    this.profile = ctx.profileContext
     this.outputBytes = (config as Required<Config>).outputBytes
     this.notificationDelayMs = (config as Required<Config>).notificationDelayMs
     this.lockWaitMs = (config as Required<Config>).lockWaitMs
@@ -95,7 +95,7 @@ export class PluginManager extends TypertRemoteService {
    */
   @Remote
   async listPlugins(): Promise<PluginInfo[]> {
-    const rows = flatten(this.runtime.entries())
+    const rows = flatten(composeEntries([readProfilePatches('dsh', this.profile)]))
     const snapshot = await readPluginInventory(this.ctx)
     return snapshot.entries.map((entry) => {
       const actual = [...this.ctx.loader.entries()].find(row => row.id === entry.entryId)
@@ -118,16 +118,16 @@ export class PluginManager extends TypertRemoteService {
    */
   @Remote
   listBundles(): Promise<BundleInfo[]> {
-    const manifest = readProfileManifest('dsh', this.runtime.dir)
+    const manifest = readProfileManifest('dsh', this.profile.dir)
     const selected = manifest.dsh?.profile?.bundles ?? []
     const dependencies = Object.keys(manifest.dependencies ?? {})
-    const installation = JSON.parse(readFileSync(this.runtime.installAnchor, 'utf8')) as { dependencies?: Record<string, string> }
+    const installation = JSON.parse(readFileSync(this.profile.installAnchor, 'utf8')) as { dependencies?: Record<string, string> }
     const names = [...new Set([...selected, ...dependencies, ...Object.keys(installation.dependencies ?? {})])]
     const bundles: BundleInfo[] = []
     for (const name of names) {
       const removable = dependencies.includes(name) && !Object.hasOwn(installation.dependencies ?? {}, name)
       try {
-        const info = bundleManifest(name, this.runtime.dir, this.runtime.installAnchor)
+        const info = bundleManifest(name, this.profile.dir, this.profile.installAnchor)
         if (info === undefined) {
           if (selected.includes(name)) bundles.push({ name, enabled: true, removable, error: `Not a bundle: ${name}` })
           continue
@@ -156,10 +156,10 @@ export class PluginManager extends TypertRemoteService {
       const row = (await this.listPlugins()).find(item => item.entryId === id)
       if (row === undefined) throw new Error(`Unknown plugin entry: ${id}`)
       if (row.readOnlyReason !== undefined || row.patchId === undefined) throw new Error(row.readOnlyReason)
-      await writePluginEnabled(this.runtime.read().patchPath, row.patchId, enabled)
-      await this.runtime.reload()
+      await writePluginEnabled(this.profile.patchPath, row.patchId, enabled)
+      await this.reload()
       const current = (await this.listPlugins()).find(item => item.entryId === id)
-      return current?.enabled !== enabled && this.runtime.patchReload === 'live' ? 'overridden' : undefined
+      return current?.enabled !== enabled && this.profile.patchReload === 'live' ? 'overridden' : undefined
     }, `Plugin ${id}: ${enabled ? 'enabled' : 'disabled'}.`)
   }
 
@@ -172,7 +172,7 @@ export class PluginManager extends TypertRemoteService {
   setBundleEnabled(name: string, enabled: boolean): Promise<ChangeResult> {
     return this.change(async () => {
       await this.selectBundle(name, enabled)
-      await this.runtime.reload()
+      await this.reload()
     }, `Bundle ${name}: ${enabled ? 'enabled' : 'disabled'}.`)
   }
 
@@ -186,22 +186,22 @@ export class PluginManager extends TypertRemoteService {
     let packageResult: PackageResult | undefined
     return this.change(async () => {
       if (spec.trim() === '' || spec.startsWith('-')) throw new Error('A package spec is required')
-      const before = readProfileManifest('dsh', this.runtime.dir).dependencies ?? {}
+      const before = readProfileManifest('dsh', this.profile.dir).dependencies ?? {}
       packageResult = await this.runPnpm(['add', spec])
       if (packageResult.exitCode !== 0) throw new Error(packageResult.output)
-      const after = readProfileManifest('dsh', this.runtime.dir).dependencies ?? {}
+      const after = readProfileManifest('dsh', this.profile.dir).dependencies ?? {}
       const installed = Object.keys(after).filter(name => before[name] !== after[name])
       // Registry retries can retain the saved range after a partial installation.
       if (installed.length === 0) installed.push(...Object.keys(after).filter(name => spec === name || spec.startsWith(`${name}@`)))
       const name = installed[0]
       if (installed.length !== 1 || name === undefined) throw new Error('Cannot identify one installed bundle from the dependency change')
-      const dir = resolveBundleDir('dsh', name, this.runtime.installAnchor, this.runtime.dir)
-      const manifest = bundleManifest(name, this.runtime.dir, this.runtime.installAnchor)
+      const dir = resolveBundleDir('dsh', name, this.profile.installAnchor, this.profile.dir)
+      const manifest = bundleManifest(name, this.profile.dir, this.profile.installAnchor)
       if (manifest?.dsh?.bundle?.patch === undefined) throw new Error(`${name} declares no dsh.bundle.patch`)
       loadOverlayPatches('dsh', join(dir, manifest.dsh.bundle.patch))
       if (options?.enabled !== false) await this.selectBundle(name, true)
       if (Object.hasOwn(before, name)) return 'restart-required'
-      await this.runtime.reload()
+      await this.reload()
     }, `Bundle installation: ${spec}.`, () => packageResult)
   }
 
@@ -215,19 +215,19 @@ export class PluginManager extends TypertRemoteService {
     return this.change(async () => {
       const bundle = (await this.listBundles()).find(item => item.name === name)
       if (bundle === undefined || !bundle.removable) throw new Error(`Bundle is not a profile-owned dependency: ${name}`)
-      if (this.runtime.patchReload === 'startup' && this.runtime.startedBundles.includes(name)) {
+      if (this.profile.patchReload === 'startup' && this.profile.startedBundles.includes(name)) {
         throw new Error('Stop this startup-only profile and remove the bundle with dsh plugin')
       }
       await this.selectBundle(name, false)
-      await this.runtime.reload()
+      await this.reload()
       packageResult = await this.runPnpm(['remove', name])
       if (packageResult.exitCode !== 0) throw new Error(packageResult.output)
-      await this.runtime.reload()
+      await this.reload()
     }, `Bundle removed: ${name}.`, () => packageResult)
   }
 
   private async runPnpm(args: readonly string[]): Promise<PackageResult> {
-    const task = runProfilePnpm({ ...this.runtime, profile: this.runtime.name }, args, {
+    const task = runProfilePnpm({ ...this.profile, profile: this.profile.name }, args, {
       signal: this.abort.signal, outputBytes: this.outputBytes, activateNewBundles: false,
     })
     this.packageOperations.add(task)
@@ -236,9 +236,9 @@ export class PluginManager extends TypertRemoteService {
   }
 
   private async selectBundle(name: string, enabled: boolean): Promise<void> {
-    const manifest = readProfileManifest('dsh', this.runtime.dir)
+    const manifest = readProfileManifest('dsh', this.profile.dir)
     const previous = manifest.dsh?.profile?.bundles ?? []
-    if ((enabled || !previous.includes(name)) && bundleManifest(name, this.runtime.dir, this.runtime.installAnchor) === undefined) {
+    if ((enabled || !previous.includes(name)) && bundleManifest(name, this.profile.dir, this.profile.installAnchor) === undefined) {
       throw new Error(`Not a bundle: ${name}`)
     }
     if (!enabled && previous.includes(name)) {
@@ -247,25 +247,30 @@ export class PluginManager extends TypertRemoteService {
     const bundles = enabled ? [...previous, ...previous.includes(name) ? [] : [name]] : previous.filter(item => item !== name)
     if (JSON.stringify(previous) === JSON.stringify(bundles)) return
     manifest.dsh = { ...manifest.dsh, profile: { ...manifest.dsh?.profile, bundles } }
-    await saveManifest(this.runtime.dir, manifest)
+    await saveManifest(this.profile.dir, manifest)
   }
 
   private protectsManager(name: string): boolean {
-    const info = bundleManifest(name, this.runtime.dir, this.runtime.installAnchor)
+    const info = bundleManifest(name, this.profile.dir, this.profile.installAnchor)
     if (info?.dsh?.bundle === undefined) return false
-    const dir = resolveBundleDir('dsh', name, this.runtime.installAnchor, this.runtime.dir)
+    const dir = resolveBundleDir('dsh', name, this.profile.installAnchor, this.profile.dir)
     const rows = flatten(composeEntries([loadOverlayPatches('dsh', join(dir, info.dsh.bundle.patch))]))
     return rows.some(row => protectedModules.has(row.name) || `include:${row.id}` === this.ownerEntryId)
   }
 
+  private async reload(): Promise<void> {
+    if (this.profile.patchReload === 'startup') return
+    await reconcileProfilePatches(this.ownerContext.root, readProfilePatches('dsh', this.profile), 'dsh')
+  }
+
   private async change(
     operation: () => Promise<ChangeResult['application'] | void>, message: string, packageResult?: () => PackageResult | undefined,
   ): Promise<ChangeResult> {
     let notice: Promise<void> | undefined
-    const result = await this.runtime.mutate(async () => {
+    const locked = () => withFileLock(join(this.profile.dir, 'package.json'), async () => {
       this.abort.signal.throwIfAborted()
       const before = this.diskState()
-      let application: ChangeResult['application'] = this.runtime.patchReload === 'live' ? 'applied' : 'restart-required'
+      let application: ChangeResult['application'] = this.profile.patchReload === 'live' ? 'applied' : 'restart-required'
       try {
         application = await operation() ?? application
       } catch (error) {
@@ -277,14 +282,16 @@ export class PluginManager extends TypertRemoteService {
       if (packages !== undefined) result.packageResult = packages
       notice = this.notify(result)
       return result
-    }, this.lockWaitMs)
+    }, { waitMs: this.lockWaitMs })
+    const hmr = this.ownerContext.get('hmr')
+    const result = await (hmr === undefined ? locked() : hmr.runExclusive(locked))
     await notice
     return result
   }
 
   private diskState(): string {
     return ['package.json', 'cordis.patch.yml'].map((file) => {
-      try { return readFileSync(join(this.runtime.dir, file), 'utf8') }
+      try { return readFileSync(join(this.profile.dir, file), 'utf8') }
       catch (error) {
         if ((error as NodeJS.ErrnoException).code === 'ENOENT') return ''
         throw error
@@ -298,7 +305,7 @@ export class PluginManager extends TypertRemoteService {
     for (const agent of agents) this.noticeAgents.add(agent)
     this.noticeDelivered ??= Promise.withResolvers<void>()
     const delivered = this.noticeDelivered.promise
-    const notice = `Profile ${this.runtime.name}: ${result.message} (${result.application})`
+    const notice = `Profile ${this.profile.name}: ${result.message} (${result.application})`
     if (Buffer.byteLength(this.pendingNotice + notice) > this.outputBytes) {
       this.omittedNotices += 1
     } else {

+ 32 - 26
packages/boot/plugin-manager/tests/manager.spec.ts

@@ -7,12 +7,13 @@ import type AgentRegistry from '@deepseek-ai/dsh-agent'
 import { Session, SessionId } from '@deepseek-ai/dsh-session'
 import type { UserMessage } from '@deepseek-ai/dsh-llm'
 import { expect, it, onTestFinished, vi } from 'vitest'
-import { withFileLock } from '@deepseek-ai/dsh-atomic-write'
 import {
-  boot, composeEntries, initProfile, loadProfileDirectory, readProfileManifest, reconcileProfilePatches,
-  type ProfileRuntime,
+  boot, composeEntries, initProfile, readProfilePatches, readProfileManifest,
+  type ProfileContext,
 } from '@deepseek-ai/dsh-app-boot'
 import PluginManager, { type Config } from '../src/index.ts'
+import Hmr from '@deepseek-ai/dsh-hmr'
+import Timer from '@deepseek-ai/cordis-plugin-timer'
 import { Group } from '@deepseek-ai/cordis-plugin-loader'
 import * as operations from '../src/operations.ts'
 
@@ -34,24 +35,20 @@ async function fixture(reload: 'live' | 'startup' = 'live', overlay = false, pre
   const manifest = readProfileManifest('test', dir)
   manifest.dependencies = { extra: '1.0.0' }
   writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
-  const read = () => loadProfileDirectory('test', dir, anchor)
-  const patches = () => [...read().layers.flatMap(layer => layer.patches), ...read().patches,
-    ...overlay ? [{ id: 'managed', disabled: true }] : []]
   writeFileSync(join(dir, 'cordis.yml'), '[]\n')
-  const runtime: ProfileRuntime = {
+  const profile: ProfileContext = {
+    name: 'test',
     startedBundles: ['core', 'extra'],
-    name: 'test', dir, installAnchor: anchor, cwd: home, home, patchReload: reload, read,
-    entries: () => composeEntries([patches()]),
-    mutate: operation => withFileLock(join(dir, 'package.json'), operation),
-    reload: async () => { if (reload === 'live') await reconcileProfilePatches(ctx, patches(), 'test') },
+    dir, patchPath: join(dir, 'cordis.patch.yml'), installAnchor: anchor, cwd: home, home, patchReload: reload,
+    overlays: overlay ? [{ id: 'managed', disabled: true }] : [], telemetryDisabledEnv: undefined,
   }
-  const ctx = await boot('test', join(dir, 'cordis.yml'), patches(), (ctx) => {
+  const ctx = await boot('test', join(dir, 'cordis.yml'), readProfilePatches('test', profile), (ctx) => {
     prepare?.(ctx)
-    ctx.provide('profileRuntime', runtime)
+    ctx.provide('profileContext', profile)
     ctx.loader.builtins.manager = PluginManager
   })
   onTestFinished(async () => { await ctx.fiber.dispose(); rmSync(home, { recursive: true, force: true }) })
-  return { ctx, dir, manager: ctx.pluginManager, bundle, runtime }
+  return { ctx, dir, manager: ctx.pluginManager, bundle, profile }
 }
 
 it('lists bundle versions and current-profile plugin targets', async () => {
@@ -201,8 +198,8 @@ it('combines concurrent changes into durable notices without waking Agents', asy
 
 
 it('reports plain dependencies, missing versions and invalid selected bundles distinctly', async () => {
-  const { manager, dir, runtime } = await fixture()
-  writeFileSync(runtime.installAnchor, '{}')
+  const { manager, dir, profile } = await fixture()
+  writeFileSync(profile.installAnchor, '{}')
   writeFileSync(join(dir, 'node_modules', 'extra', 'package.json'), '{"name":"extra"}')
   expect((await manager.listBundles()).find(row => row.name === 'extra')).toMatchObject({ enabled: true, error: 'Not a bundle: extra' })
   expect(await manager.setBundleEnabled('extra', false)).toMatchObject({ application: 'applied' })
@@ -213,7 +210,7 @@ it('reports plain dependencies, missing versions and invalid selected bundles di
   writeFileSync(join(dir, 'package.json'), '{}')
   expect(await manager.listBundles()).toEqual([])
   expect(await manager.setBundleEnabled('unknown', false)).toMatchObject({ application: 'failed' })
-  writeFileSync(runtime.installAnchor, '{"dependencies":{"missing-builtin":"1"}}')
+  writeFileSync(profile.installAnchor, '{"dependencies":{"missing-builtin":"1"}}')
   expect(await manager.listBundles()).toEqual([])
 })
 
@@ -224,15 +221,14 @@ it('refuses management bundle disablement and permits repeated bundle selections
 })
 
 it('addresses children inside profile groups and marks ambiguous ids read-only', async () => {
-  const { manager, bundle, runtime } = await fixture('live', false, (ctx) => { ctx.loader.builtins.group = Group })
+  const { manager, bundle, profile } = await fixture('live', false, (ctx) => { ctx.loader.builtins.group = Group })
   bundle('grouped', [{ id: 'group', name: 'cordis:group', group: true,
     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()
-  const entries = runtime.entries()
+  const entries = composeEntries([readProfilePatches('test', profile)])
   const duplicate = entries.find(row => row.id === 'managed')!
-  const read = vi.spyOn(runtime, 'entries').mockReturnValue([...entries, duplicate])
-  onTestFinished(() => { read.mockRestore() })
+  writeFileSync(profile.patchPath, JSON.stringify([{ insert: [duplicate] }]))
   expect((await manager.listPlugins()).find(row => row.entryId === 'include:managed')?.readOnlyReason).toContain('not uniquely addressable')
 })
 
@@ -270,13 +266,14 @@ it('reports repeated installs as requiring restart and ambiguous package changes
   expect(await manager.installBundle('unknown')).toMatchObject({ changed: true, application: 'failed' })
 })
 
-it('handles missing patch files and retains non-Error Loader diagnostics', async () => {
-  const { manager, dir, runtime } = await fixture()
+it('handles missing patch files and retains non-Error package diagnostics', async () => {
+  const { manager, dir } = await fixture()
   rmSync(join(dir, 'cordis.patch.yml'))
   const id = (await manager.listPlugins()).find(row => row.patchId === 'managed')!.entryId
-  const reload = vi.spyOn(runtime, 'reload').mockRejectedValueOnce('loader rejected generation')
-  onTestFinished(() => { reload.mockRestore() })
-  expect(await manager.setPluginEnabled(id, false)).toMatchObject({ changed: true, application: 'failed', message: 'loader rejected generation' })
+  expect(await manager.setPluginEnabled(id, false)).toMatchObject({ changed: true, application: 'applied' })
+  const install = vi.spyOn(operations, 'runProfilePnpm').mockRejectedValueOnce('pnpm rejected operation')
+  onTestFinished(() => { install.mockRestore() })
+  expect(await manager.installBundle('new')).toMatchObject({ changed: false, application: 'failed', message: 'pnpm rejected operation' })
   rmSync(join(dir, 'cordis.patch.yml'))
   mkdirSync(join(dir, 'cordis.patch.yml'))
   await expect(manager.setPluginEnabled(id, true)).rejects.toThrow()
@@ -290,3 +287,12 @@ it('bounds batched notices and discloses omitted operation results', async () =>
   await manager.setBundleEnabled('extra', false)
   expect(JSON.stringify(messages)).toContain('1 additional operations omitted')
 })
+
+it('applies a manager change through the active HMR service', async () => {
+  const { ctx, manager } = await fixture()
+  await ctx.plugin(Timer)
+  await ctx.plugin(Hmr, { root: [], ignored: [], debounce: 0 })
+  const id = (await manager.listPlugins()).find(row => row.patchId === 'managed')!.entryId
+  expect(await manager.setPluginEnabled(id, false)).toMatchObject({ changed: true, application: 'applied' })
+  expect((await manager.listPlugins()).find(row => row.entryId === id)?.enabled).toBe(false)
+})

+ 6 - 0
packages/boot/plugin-manager/tsconfig.json

@@ -43,6 +43,12 @@
     },
     {
       "path": "../../util/values"
+    },
+    {
+      "path": "../hmr"
+    },
+    {
+      "path": "../../../vendor/timer"
     }
   ]
 }

+ 2 - 2
packages/bundle/base/cordis.patch.yml

@@ -15,11 +15,11 @@
 - insert:
     - id: tool-plugin-manager
       name: '@deepseek-ai/dsh-plugin-manager/tools'
-      disabled: !!js "!ctx.get('profileRuntime')"
+      disabled: true
 
     - id: plugin-manager
       name: '@deepseek-ai/dsh-plugin-manager'
-      disabled: !!js "!ctx.get('profileRuntime')"
+      disabled: !!js "!ctx.get('profileContext')"
 
     - id: timer
       name: '@deepseek-ai/cordis-plugin-timer'

+ 4 - 1
packages/bundle/web-app/cordis.patch.yml

@@ -159,7 +159,10 @@
         surfaceContext: true
         trustedHosts: !!js ctx.webStartup.trustedHosts
 
-    # Synchronize browser plugin membership and development bundle rebuilds.
+    # The client-plugin reload chain, always mounted: it is idle until a
+    # rebuild watcher (pnpm run dev:web) actually rewrites client bundles. It
+    # is a row rather than a child of web-runtime because its node half is a
+    # client-side package, which a host-side bundle cannot import.
     - id: client-hmr
       name: '@deepseek-ai/dsh-client-hmr'
 

+ 2 - 2
packages/client/hmr/README.i18n.yaml

@@ -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/client/hmr/README.md
-README.md: c35100c31e5e8ccb78a9e3d63864c55481b1bf29
-README.zh.md: 96e5c9f19321053cba12da9532a5a1d029b0698b
+README.md: 26bc640c6cdd213fbc78de920e12444589901990
+README.zh.md: 72d941f678e7e29a56bca678a79c4740667e7e6b

+ 4 - 6
packages/client/hmr/README.md

@@ -1,5 +1,5 @@
 ---
-description: "Synchronize browser plugins with Host composition and reload rebuilt client bundles."
+description: "Development-only hot reload for browser client plugins: rebuilding a plugin bundle swaps the running plugin in place, for developers iterating on the web GUI."
 kind: "package-reference"
 ---
 
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
 
 ## Summary
 
-`dsh-client-hmr` keeps browser plugins synchronized with the Host module graph. Enabling or disabling a bundle adds or removes its browser contributions without a page reload. Development rebuilds replace existing plugins with fresh component state; retained plugins and Session state remain available.
+`dsh-client-hmr` reloads a browser client plugin in place when its bundle is rebuilt, so a developer editing plugin source sees the change without a full page reload. The reload chain stays idle without a rebuild watcher: only a `pnpm run dev:web`-style process rewriting client bundles produces the rebuilds it reacts to. Each reload swaps one plugin with fresh component state while the data layer (connection, runtime, and Session objects) stays untouched. Everything here is development machinery in the browser; the model never sees it.
 
 ## Table of Contents
 
@@ -25,7 +25,7 @@ English | [中文](README.zh.md)
 <a id="use-this-package"></a>
 ## Use this package
 
-The Web profile loads this package automatically. Bundle changes use the existing `/plugins/events` channel. For source development, enable a process that rebuilds the edited plugin’s `lib/client.js`.
+Enable the rebuild watcher for the plugin you are editing, then save: the browser picks up the rebuilt bundle from the dev server and swaps the plugin without reloading the page. Use it during client development; nothing observable happens in a production build, where no watcher rewrites bundles.
 
 ### Starting the reload chain
 
@@ -65,8 +65,6 @@ The chain is two halves with one contract: the node half owns bundle detection a
 
 On a `rebuilt` frame the revision makes `invalidate` select that plugin's immutable one-resource combo URL instead of its initial multi-resource URL. `prefetch` loads and registers the new factory while the old fiber still serves. The remaining order is registry-first teardown (`registry.delete` before the fiber's disposer emits `internal/plugin`, or the vendored Loader flags the entry disabled), drain the old fiber's unload, delete `entry.fiber`, remove owned `<style data-plugin>` tags, then `entry.refresh()` re-imports and remounts, and `fiber.await()` rethrows startup failures loudly. The swap is safe because execution is pure registration under the lazy-CJS model: every module side effect lives in the factory closure and runs at materialization.
 
-Graph frames use the same queue as rebuilt frames. Removed fibers finish unloading before module records and owned styles are removed. Incoming rows use individual artifact requests, so loading a new plugin does not execute retained factories again. The module system validates each graph before changing its routing table.
-
 ### Cascade and self-reload
 
 A fiber's activation epoch strings its service providers' uids, so replacing a provider's fiber re-cascades every dependent through Cordis itself with zero HMR-side bookkeeping. This plugin is itself a graph entry, so a rebuilt frame may name it; the in-flight reload keeps running in the old bundle's closure and the new bundle's apply opens a fresh channel.
@@ -117,7 +115,7 @@ These limits define what the reload driver does not preserve or restore. They ar
 
 - **Reload is coarse by design** — a fresh fiber and fresh components; React state inside the reloaded plugin is lost while the data layer (connection/runtime fibers, Session objects) is untouched. react-refresh-grade state preservation conflicts with re-executing the bundle and is deliberately out.
 - **No failure rollback** — a reload that fails leaves the entry FAILED and visible in the loader status projection; the previous bundle is not restored automatically.
-- **Plugin contributions only** — shell and platform-library replacement still requires a page reload.
+- **Rebuilt frames do not replace the boot graph** — each frame carries the plugin-artifact revision needed for its one-resource combo reload; a page reload receives the recomposed startup graph.
 
 <a id="dev-note"></a>
 ### Dev Note

+ 4 - 6
packages/client/hmr/README.zh.md

@@ -1,5 +1,5 @@
 ---
-description: "同步 Host 组合中的浏览器插件,并重载重新构建的客户端 bundle。"
+description: "仅用于开发环境的浏览器客户端插件热重载:重建插件 bundle 后原地替换运行中的插件,供开发者迭代 web GUI。"
 kind: "package-reference"
 ---
 
@@ -9,7 +9,7 @@ kind: "package-reference"
 
 ## 概述
 
-`dsh-client-hmr` 让浏览器插件与 Host 模块图保持同步。开启或关闭 bundle 会新增或移除其浏览器贡献,无需刷新页面。开发构建会以全新的组件状态替换现有插件;保留的插件与 Session 状态继续可用
+`dsh-client-hmr` 会在浏览器客户端插件的 bundle 重建后原地重载该插件,让编辑插件源码的开发者无需整页刷新即可看到变更。如果没有重建 watcher,整条链路保持空闲:只有 `pnpm run dev:web` 之类的进程重写客户端 bundle 时才会产生它所响应的重建。每次重载只替换一个插件并携带全新组件状态,而数据层(连接、运行时与 Session 对象)保持不变。这里的一切都是浏览器侧的开发机制;模型永远看不到它
 
 ## 目录
 
@@ -25,7 +25,7 @@ kind: "package-reference"
 <a id="use-this-package"></a>
 ## 使用本包
 
-Web profile 自动加载本包。Bundle 变更使用现有的 `/plugins/events` 通道。开发源码时,启用重建目标插件 `lib/client.js` 的进程
+为正在编辑的插件启用重建 watcher,然后保存:浏览器会从 dev server 拾取重建后的 bundle,并在不重载页面的情况下替换该插件。在客户端开发期间使用它;在生产构建中没有任何可观察行为,因为没有 watcher 会重写 bundle
 
 ### 启动重载链路
 
@@ -65,8 +65,6 @@ Web profile 自动加载本包。Bundle 变更使用现有的 `/plugins/events`
 
 收到 `rebuilt` 帧后,帧内 revision 会让 `invalidate` 选择该插件不可变的单资源 combo URL,而不是初始多资源 URL。`prefetch` 在旧 fiber 仍在服务时加载并注册新 factory。其余顺序是:先从注册表删除,再拆卸(在 fiber 的 disposer 发出 `internal/plugin` 之前执行 `registry.delete`,否则 vendored Loader 会把该 entry 标为禁用)、等待旧 fiber 卸载完成、删除 `entry.fiber`、移除自身拥有的 `<style data-plugin>` 标签,然后 `entry.refresh()` 重新导入并挂载,`fiber.await()` 直接把启动失败重新抛出。替换之所以安全,是因为在惰性 CJS 模型下执行只是注册:每个模块副作用都位于 factory 闭包中,在物化时运行。
 
-Graph 帧与 rebuilt 帧使用同一队列。被移除的 fiber 完成卸载后,才清除模块记录和归属样式。新增条目独立请求其产物,避免再次执行已有工厂。模块系统先校验每个模块图,再更新路由表。
-
 ### 级联与自重载
 
 fiber 的激活 epoch 会串联其服务提供方的 uid,因此替换提供方 fiber 会通过 Cordis 自身级联重载所有依赖方,无需 HMR(热模块替换)侧维护任何簿记信息。本插件本身也是一个图 entry,因此 `rebuilt` 帧可能点名它;进行中的重载在旧 bundle 的闭包中继续运行,新 bundle 的 apply 会打开全新通道。
@@ -117,7 +115,7 @@ fiber 的激活 epoch 会串联其服务提供方的 uid,因此替换提供方
 
 - **重载有意保持粗粒度**——全新 fiber 与全新组件;被重载插件内的 React 状态会丢失,而数据层(连接 fiber、运行时 fiber、Session 对象)不受影响。react-refresh 级状态保留与重新执行 bundle 冲突,因此有意排除。
 - **失败时不回滚**——失败的重载会让该 entry 保持 FAILED 并在 loader 状态投影中可见;系统不会自动恢复先前 bundle。
-- **仅同步插件贡献** — 替换 shell 和平台库仍需要刷新页面
+- **重建帧不会替换启动图**——每个帧都携带单资源 combo 重载所需的插件产物 revision;页面重载时才接收重新组合的启动图
 
 <a id="dev-note"></a>
 ### 开发备注

+ 2 - 5
packages/client/hmr/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@deepseek-ai/dsh-client-hmr",
-  "description": "Hot reload for browser client plugins: reconcile Host module graphs and swap rebuilt bundles through Cordis Loader",
+  "description": "Dev-only hot-reload driver for script-loaded client entries: SSE rebuilt frames → invalidate/prefetch → fiber swap through the vendored Loader entry",
   "version": "0.1.5-rc.2",
   "publishConfig": {
     "access": "public"
@@ -35,10 +35,7 @@
         "@deepseek-ai/dsh-client-modules"
       ],
       "platform": "web",
-      "immediately": true,
-      "external": [
-        "@deepseek-ai/dsh-client-modules/client"
-      ]
+      "immediately": true
     }
   },
   "license": "MIT",

+ 5 - 46
packages/client/hmr/src/client/index.ts

@@ -43,7 +43,7 @@
  *    (vendor/loader/src/index.ts `internal/plugin` case 4: the registry
  *    still holds the runtime at emit time), which flags the entry
  *    `disabled: true` — permanently.
- * dsh-hmr's module replacement documents the fix: delete the runtime record
+ * vendor/hmr's reload skeleton documents the fix: delete the runtime record
  * FIRST (`registry.delete` → case 4 returns early, the entry stays enabled),
  * then rebuild. `entry.fiber` is additionally cleared so
  * `entry.refresh()` re-imports and re-plugins through the Loader's own
@@ -65,8 +65,6 @@ import type { Context } from '@deepseek-ai/cordis'
 import type { Entry, Loader } from '@deepseek-ai/cordis-plugin-loader'
 import type { PluginsEventFrame } from '../events.ts'
 import { EVENTS_ENDPOINT, parsePluginsEventFrame } from '../events.ts'
-import { parseBootManifest } from '@deepseek-ai/dsh-client-modules/client'
-import type { BootManifest } from '@deepseek-ai/dsh-client-modules/client'
 
 export type { PluginsEventFrame } from '../events.ts'
 export { EVENTS_ENDPOINT } from '../events.ts'
@@ -120,7 +118,6 @@ export function apply(ctx: Context): void {
   // client module loader package, `loader` from the vendored Loader).
   const modLoader = ctx.modules
   const loader: Loader = ctx.loader
-  const revisions = new Map(modLoader.manifest.modules.map(row => [row.id, row.rev]))
 
   async function reload(id: string, rev: string): Promise<void> {
     const entry = findEntry(loader, id)
@@ -147,38 +144,6 @@ export function apply(ctx: Context): void {
     await entry.refresh()
     // Surface apply failures loudly (no rollback, FAILED state stays).
     await entry.fiber?.await()
-    revisions.set(id, rev)
-  }
-
-  async function reconcile(manifest: BootManifest): Promise<void> {
-    const incoming = new Set(manifest.plugins.map(row => row.id))
-    for (const row of [...modLoader.manifest.plugins].reverse()) {
-      if (incoming.has(row.id)) continue
-      const entry = findEntry(loader, row.id)
-      if (entry !== undefined) {
-        await tearDownEntryFiber(entry)
-        entry.parent.remove(entry.options.id)
-      }
-      removeOwnedStyles(row.id)
-      revisions.delete(row.id)
-    }
-    modLoader.updateManifest(manifest)
-    const added = []
-    for (const row of manifest.modules) {
-      const entry = findEntry(loader, row.id)
-      if (entry === undefined) {
-        added.push(await loader.create({ name: row.id }))
-      } else if (revisions.get(row.id) !== row.rev) {
-        await reload(row.id, row.rev)
-      }
-      revisions.set(row.id, row.rev)
-    }
-    await loader.await()
-    for (const id of added) {
-      const entry = loader.resolve(id)
-      if (entry.fiber === undefined) throw new Error(`client-hmr: import failed for "${entry.options.name}"`)
-      await entry.fiber.await()
-    }
   }
 
   // Serialize reloads: frames can arrive faster than a swap completes, and
@@ -187,21 +152,15 @@ export function apply(ctx: Context): void {
   const handle = (frame: PluginsEventFrame): void => {
     switch (frame.type) {
       case 'rebuilt':
-        queue = queue.then(async () => {
-          if (revisions.get(frame.id) !== frame.rev) await reload(frame.id, frame.rev)
-        }).catch((error: unknown) => {
+        queue = queue.then(() => reload(frame.id, frame.rev)).catch((error: unknown) => {
           ctx.logger.error(`client-hmr: reload of "${frame.id}" failed`)
           ctx.logger.error(error)
         })
         break
       case 'graph':
-        queue = queue.then(async () => {
-          const manifest = parseBootManifest(frame.graph)
-          if (manifest.rev !== modLoader.manifest.rev) await reconcile(manifest)
-        }).catch((error: unknown) => {
-          ctx.logger.error('client-hmr: graph update failed')
-          ctx.logger.error(error)
-        })
+        // Connect-time snapshot, unused. Each rebuilt frame carries the
+        // revision that selects the immutable single-resource combo script; the boot
+        // graph remains the initial-load record until a page reload.
         break
       default:
         // Merge-extensible frame union: unknown frame types from newer hosts

+ 0 - 5
packages/client/hmr/src/index.ts

@@ -191,12 +191,7 @@ export function apply(ctx: Context, config: Config): void {
       const line = sseData({ type: 'rebuilt', id, rev })
       for (const res of connections) res.write(line)
     })
-    const unsubscribeGraph = ctx.clientModules.onGraphChanged(() => {
-      const line = sseData({ type: 'graph', graph: ctx.clientModules.graph() })
-      for (const res of connections) res.write(line)
-    })
     return () => {
-      unsubscribeGraph()
       unsubscribe()
       disposeRoute()
       for (const res of connections) res.destroy()

+ 0 - 64
packages/client/hmr/tests/graph.client.spec.ts

@@ -1,64 +0,0 @@
-// @vitest-environment jsdom
-/** Live graph changes through the browser module system and real Cordis Loader. */
-import { Context } from '@deepseek-ai/cordis'
-import Loader from '@deepseek-ai/cordis-plugin-loader'
-import { createClientModuleSystem, type ClientModuleLoaderTarget, type WebBootGraph } from '@deepseek-ai/dsh-client-modules/client'
-import { expect, it, onTestFinished, vi } from 'vitest'
-import * as hmr from '../src/client/index.ts'
-
-it('adds, removes and re-adds client contributions without reloading retained modules', async () => {
-  class Source extends EventTarget {
-    static current: Source
-    constructor() { super(); Source.current = this }
-    close() {}
-  }
-  vi.stubGlobal('EventSource', Source)
-  onTestFinished(() => { vi.unstubAllGlobals() })
-  const ctx = new Context()
-  ctx.baseUrl = 'https://dsh.invalid/'
-  onTestFinished(() => ctx.fiber.dispose())
-  await ctx.plugin(Loader)
-  let mounted = 0
-  let disposed = 0
-  const imports: string[] = []
-  const graph = (rev: string, ids: string[]): WebBootGraph => ({
-    rev, entries: ids.map(id => ({ id, url: `/plugins/${id}.js`, rev })),
-    batches: ids.length === 0 ? [] : [{ phase: 'application', url: '/batch.js', rev, entries: ids }],
-  })
-  const target: ClientModuleLoaderTarget = {
-    mode: 'queue', pendingQueue: [], load() {},
-    create() { throw new Error('already created') },
-  }
-  const modules = createClientModuleSystem(target, { id: 'bootstrap', exports: {} }, {
-    boot: graph('empty', []), staticModules: {},
-    loadBundle: async (url) => {
-      imports.push(url)
-      target.load({ id: 'added', factory: () => ({ apply(host: Context) {
-        mounted++
-        host.effect(() => {
-          const style = document.createElement('style')
-          style.setAttribute('data-plugin', 'added')
-          document.head.append(style)
-          return () => { disposed++ }
-        })
-      } }) })
-    },
-  })
-  ctx.loader.internal = modules as never
-  ctx.provide('modules', modules)
-  await ctx.plugin(hmr)
-  const send = (value: unknown) => Source.current.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(value) }))
-  send({ type: 'graph', graph: graph('one', ['added']) })
-  await vi.waitFor(() => { expect(mounted).toBe(1) })
-  expect([...ctx.loader.entries()].filter(row => row.options.name === 'added')).toHaveLength(1)
-  expect(imports).toEqual(['/plugins/added.js'])
-  send({ type: 'graph', graph: graph('one', ['added']) })
-  send({ type: 'graph', graph: graph('removed', []) })
-  await vi.waitFor(() => { expect(disposed).toBe(1) })
-  expect([...ctx.loader.entries()].filter(row => row.options.name === 'added')).toHaveLength(0)
-  expect(document.querySelector('style[data-plugin="added"]')).toBeNull()
-  expect(modules.loadCache.has('added')).toBe(false)
-  send({ type: 'graph', graph: graph('two', ['added']) })
-  await vi.waitFor(() => { expect(mounted).toBe(2) })
-  expect(imports).toEqual(['/plugins/added.js', '/plugins/added.js'])
-})

+ 2 - 2
packages/client/modules/README.i18n.yaml

@@ -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/client/modules/README.md
-README.md: 11c3d1ae98619d7cd327c8f2620b68cb3e93c695
-README.zh.md: 6f103828bf155dac6c766ba0d26ecbd3eea59454
+README.md: c83796fc8d6c364b9fad6d44e25316e2c2f7e7af
+README.zh.md: 9ffb68ebc07b5970edc45a54c4cfde7cdf4ef7d8

+ 0 - 2
packages/client/modules/README.md

@@ -69,8 +69,6 @@ The Node half scans incrementally per package — no full-rescan path. Every `in
 
 The Node half snapshots each client bundle and available source map before publication. It groups resources into `/plugins/??...&rev=...` combo URLs, with one bootstrap combo for the modules row and one or more application combos for the other rows; each phase is partitioned before a URL exceeds 3 KiB. Every combo map is Indexed Source Map v3 and uses an authored section when available or an identity section for the packaged bundle. Initial per-plugin revisions use process nonces, so startup does not hash every plugin; HMR hashes only an artifact reported as changed. Advertised responses are immutable, and an unknown combination or revision returns 404.
 
-`updateManifest()` replaces validated graph routing after the HMR driver drains removed plugin fibers. New rows request individual bundles; retained factories stay registered. Removed rows lose their cached factories and exports.
-
 ### Boot manifest injection
 
 The bundle route follows the injected `webServer` lifetime: it registers when the service is ready and is removed and re-registered when that service is replaced. Module composition and `fetchBundle()` remain available without a Web server.

+ 0 - 2
packages/client/modules/README.zh.md

@@ -69,8 +69,6 @@ Node 半侧逐包增量扫描——没有全量重扫路径。每次发出 `inte
 
 Node 半侧会在发布前快照每个客户端 bundle 及其现有 source map。它把资源分组到 `/plugins/??...&rev=...` combo URL:modules row 使用一个 bootstrap combo,其余 row 使用一个或多个 application combo;每个阶段都会在 URL 超过 3 KiB 之前分区。每个 combo map 都是 Indexed Source Map v3,并在可用时使用作者提供的 section,否则为已打包 bundle 生成 identity section。初始逐插件 revision 使用进程 nonce,所以启动时不哈希每个插件;HMR 只哈希被报告为已变化的产物。已公告响应不可变;未知组合或 revision 返回 404。
 
-`updateManifest()` 在 HMR 驱动等待被移除插件的 fiber 完成卸载后,替换已校验的模块图路由。新增条目单独请求 bundle,保留条目的工厂继续注册。被移除条目的工厂和导出缓存被清除。
-
 ### 启动 manifest 注入
 
 bundle 路由随注入的 `webServer` 生命周期注册:服务就绪时注册,服务被替换时移除并重新注册。模块组合与 `fetchBundle()` 在没有 Web server 时仍可用。

+ 0 - 5
packages/client/modules/src/client/manifest.ts

@@ -406,11 +406,6 @@ export interface ClientModuleLoader {
    * the graph revision or for page-local modules that register directly.
    */
   invalidate(id: string, rev?: string): void
-  /** Replace graph routing after removed plugin fibers have finished unloading.
-   * Retained factories remain registered; incoming rows use individual bundle requests.
-   * @param manifest Validated current Host graph.
-   */
-  updateManifest(manifest: BootManifest): void
 }
 
 /** Internal construction inputs assembled by the modules bundle's bootstrap export. */

+ 1 - 12
packages/client/modules/src/client/system.ts

@@ -60,7 +60,7 @@ const claimStyles = (id: string): string[] => {
  */
 export class ClientModuleSystem implements ClientModuleLoader {
   readonly version = 'client'
-  manifest: BootManifest
+  readonly manifest: BootManifest
   readonly loadCache = new Map<string, ClientModuleRecord>()
 
   private readonly seed: Map<string, unknown>
@@ -237,17 +237,6 @@ export class ClientModuleSystem implements ClientModuleLoader {
     await this.arriveGraphRow(row)
   }
 
-  updateManifest(manifest: BootManifest): void {
-    const incoming = new Set(manifest.modules.map(row => row.id))
-    for (const id of this.graphRows.keys()) {
-      if (incoming.has(id)) continue
-      this.graphRows.delete(id)
-      this.invalidate(id)
-    }
-    for (const row of manifest.modules) this.graphRows.set(row.id, { ...row, initialUrl: row.url })
-    this.manifest = manifest
-  }
-
   invalidate(id: string, rev?: string): void {
     const normalized = stripClientSuffix(id)
     if (this.bootstrapIds.has(normalized)) return

+ 7 - 32
packages/extensions/tool-cordis/src/api-catalog.ts

@@ -1477,9 +1477,9 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
     ],
   },
   {
-    key: 'profileRuntime',
-    summary: 'Current-process profile operations; callbacks run under the shared profile write lock.',
-    description: 'Current-process profile operations; callbacks run under the shared profile write lock.',
+    key: 'profileContext',
+    summary: 'Current profile facts; scheduling and mutation belong to their callers.',
+    description: 'Current profile facts; scheduling and mutation belong to their callers.',
     methods: [
       {
         signature: 'readonly startedBundles: readonly string[]',
@@ -1487,26 +1487,13 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
         parameters: [],
       },
       {
-        signature: 'read(): Profile',
-        description: 'Read the current manifest and bundle patch layers without initializing a profile.',
+        signature: 'readonly overlays: readonly PatchOptions[]',
+        description: 'Parsed command-line overlays, applied above profile and home patches.',
         parameters: [],
-        returns: 'Resolved disk configuration.',
       },
       {
-        signature: 'entries(): EntryOptions[]',
-        description: 'Compose disk configuration with the invocation\'s higher-priority layers.',
-        parameters: [],
-        returns: 'Effective entry options in composition order.',
-      },
-      {
-        signature: 'mutate<T>(operation: () => Promise<T>, waitMs?: number): Promise<T>',
-        description: 'Serialize a mutation with file watching and other profile writers.',
-        parameters: [{ name: 'operation', description: 'Work performed while holding the profile manifest lock.' }, { name: 'waitMs', description: 'Maximum lock acquisition time; omission uses the file writer default.' }],
-        returns: 'The operation\'s result.',
-      },
-      {
-        signature: 'reload(): Promise<void>',
-        description: 'Apply the current disk configuration; call only inside mutate.',
+        signature: 'readonly telemetryDisabledEnv: string | undefined',
+        description: 'Launch-time DSH_TELEMETRY_DISABLED value; any non-empty value opts out.',
         parameters: [],
       },
     ],
@@ -5108,18 +5095,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [
     name: 'PreToolDecision',
     declaration: 'export type PreToolDecision = {\n    kind: \'allow\';\n} | {\n    kind: \'deny\';\n    reason: string;\n    info?: ToolErrorInfo;\n} | {\n    kind: \'cancel\';\n} | {\n    kind: \'ask\';\n    reason?: string;\n};',
   },
-  {
-    name: 'Profile',
-    declaration: 'export interface Profile {\n    name: string;\n    dir: string;\n    layers: ProfileLayer[];\n    patchPath: string;\n    patches: PatchOptions[];\n    patchReload: ProfilePatchReload;\n}',
-  },
-  {
-    name: 'ProfileLayer',
-    declaration: 'export interface ProfileLayer {\n    packageName: string;\n    packageDir: string;\n    patchPath: string;\n    patches: PatchOptions[];\n}',
-  },
-  {
-    name: 'ProfilePatchReload',
-    declaration: 'export type ProfilePatchReload = \'live\' | \'startup\';',
-  },
   {
     name: 'ProjectionChangeListener',
     declaration: 'export type ProjectionChangeListener = (session: Session, key: Extract<keyof SessionProjectionMap, string>, value: unknown, seq: SessionSeq) => void;',

+ 1 - 1
packages/preset/agent-presets/presets/cordis/agent.cordis.yml

@@ -274,4 +274,4 @@
 
 - id: tool-plugin-manager
   name: '@deepseek-ai/dsh-plugin-manager/tools'
-  disabled: !!js "!ctx.get('profileRuntime')"
+  disabled: true

+ 1 - 1
packages/preset/agent-presets/presets/ptc/agent.cordis.yml

@@ -284,4 +284,4 @@
 
 - id: tool-plugin-manager
   name: '@deepseek-ai/dsh-plugin-manager/tools'
-  disabled: !!js "!ctx.get('profileRuntime')"
+  disabled: true

+ 1 - 1
packages/preset/agent-presets/presets/standard/agent.cordis.yml

@@ -263,4 +263,4 @@
 
 - id: tool-plugin-manager
   name: '@deepseek-ai/dsh-plugin-manager/tools'
-  disabled: !!js "!ctx.get('profileRuntime')"
+  disabled: true

+ 6 - 0
pnpm-lock.yaml

@@ -1515,9 +1515,15 @@ importers:
       '@deepseek-ai/cordis-plugin-loader':
         specifier: workspace:^
         version: link:../../../vendor/loader
+      '@deepseek-ai/cordis-plugin-timer':
+        specifier: workspace:^
+        version: link:../../../vendor/timer
       '@deepseek-ai/dsh-agent':
         specifier: workspace:^
         version: link:../../core/agent
+      '@deepseek-ai/dsh-hmr':
+        specifier: workspace:^
+        version: link:../hmr
       '@deepseek-ai/dsh-host-plugin-inventory':
         specifier: workspace:^
         version: link:../../host/plugin-inventory

+ 1 - 1
scripts/gen-cordis-catalog.ts

@@ -55,7 +55,7 @@ export { REGION_BEGIN, REGION_END }
  */
 export const SERVICE_PAGE: Record<string, string> = {
   pluginManager: 'boot.md',
-  profileRuntime: 'boot.md',
+  profileContext: 'boot.md',
   hmr: 'boot.md',
   mcpResources: 'mcp.md',
   agentLoop: 'core.md',

+ 3 - 3
scripts/gen-doc-graphs.ts

@@ -115,12 +115,12 @@ const SERVICE_ROLES: ServiceRole[] = [
     note: 'Shares profile package operations with the CLI and reports persisted and running state to Web and agent callers.',
   },
   {
-    key: 'profileRuntime',
+    key: 'profileContext',
     pkg: 'app-boot',
-    title: 'Launcher-owned profile reload coordination',
+    title: 'Launcher-owned profile data',
     mode: 'core',
     consumers: ['plugin-manager'],
-    note: 'The dsh launcher supplies profile locations and composition; reload scheduling belongs to dsh-hmr.',
+    note: 'The dsh launcher supplies data-only profile locations and composition inputs; reload scheduling belongs to dsh-hmr.',
   },
   {
     key: 'mcpResources',

+ 0 - 80
snapshots/session/agent-instructions/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",
@@ -912,46 +872,6 @@
           }
         }
       },
-      {
-        "name": "plugin_manager",
-        "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-        "parameters": {
-          "type": "object",
-          "properties": {
-            "action": {
-              "type": "string",
-              "description": "Management operation.",
-              "enum": [
-                "list_plugins",
-                "list_bundles",
-                "set_plugin",
-                "set_bundle",
-                "install_bundle",
-                "remove_bundle"
-              ]
-            },
-            "target": {
-              "type": "string",
-              "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-            },
-            "enabled": {
-              "type": "boolean",
-              "description": "Required for set operations; defaults to true for installation."
-            },
-            "offset": {
-              "type": "number",
-              "description": "Zero-based list offset; defaults to 0."
-            },
-            "limit": {
-              "type": "number",
-              "description": "List page size, from 1 to 100; defaults to 25."
-            }
-          },
-          "required": [
-            "action"
-          ]
-        }
-      },
       {
         "name": "read",
         "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/background-confinement-failure/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 14
snapshots/session/both-mode-turn/system-prompt.expected.md

@@ -138,19 +138,6 @@ interface ToolArgsMap {
     /** children (default) lists direct children only; descendants walks the complete tree below you. */
     scope?: "children" | "descendants";
   } & Record<string, JsonValue>;
-  /** List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart. */
-  plugin_manager: {
-    /** Management operation. */
-    action: "list_plugins" | "list_bundles" | "set_plugin" | "set_bundle" | "install_bundle" | "remove_bundle";
-    /** Plugin entry id, bundle package name, or installation spec, according to action. */
-    target?: string;
-    /** Required for set operations; defaults to true for installation. */
-    enabled?: boolean;
-    /** Zero-based list offset; defaults to 0. */
-    offset?: number;
-    /** List page size, from 1 to 100; defaults to 25. */
-    limit?: number;
-  } & Record<string, JsonValue>;
   /** Read a UTF-8 text file and return line-numbered content. */
   read: {
     /** Path to read, resolved by the filesystem backend. */
@@ -399,7 +386,6 @@ interface ToolOutputMap {
     parent?: string;
     depth?: number;
   })[];
-  plugin_manager: string;
   read: {
     path: string;
     offset: number;

+ 0 - 40
snapshots/session/both-mode-turn/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/browser-use-chrome-devtools-mcp/tool-schemas.expected.json

@@ -346,46 +346,6 @@
         "$schema": "http://json-schema.org/draft-07/schema#"
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/browser-use-playwright-mcp/tool-schemas.expected.json

@@ -348,46 +348,6 @@
         "additionalProperties": false
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/browser-use-stagehand-native/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 80
snapshots/session/compaction-recovery/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",
@@ -912,46 +872,6 @@
           }
         }
       },
-      {
-        "name": "plugin_manager",
-        "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-        "parameters": {
-          "type": "object",
-          "properties": {
-            "action": {
-              "type": "string",
-              "description": "Management operation.",
-              "enum": [
-                "list_plugins",
-                "list_bundles",
-                "set_plugin",
-                "set_bundle",
-                "install_bundle",
-                "remove_bundle"
-              ]
-            },
-            "target": {
-              "type": "string",
-              "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-            },
-            "enabled": {
-              "type": "boolean",
-              "description": "Required for set operations; defaults to true for installation."
-            },
-            "offset": {
-              "type": "number",
-              "description": "Zero-based list offset; defaults to 0."
-            },
-            "limit": {
-              "type": "number",
-              "description": "List page size, from 1 to 100; defaults to 25."
-            }
-          },
-          "required": [
-            "action"
-          ]
-        }
-      },
       {
         "name": "read",
         "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/computer-use-cua-driver-mcp/tool-schemas.expected.json

@@ -325,46 +325,6 @@
         "additionalProperties": false
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/computer-use-cua-driver-native/tool-schemas.expected.json

@@ -306,46 +306,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 14
snapshots/session/cordis-inspect-jsdoc/system-prompt.expected.md

@@ -305,19 +305,6 @@ interface ToolArgsMap {
     /** children (default) lists direct children only; descendants walks the complete tree below you. */
     scope?: "children" | "descendants";
   } & Record<string, JsonValue>;
-  /** List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart. */
-  plugin_manager: {
-    /** Management operation. */
-    action: "list_plugins" | "list_bundles" | "set_plugin" | "set_bundle" | "install_bundle" | "remove_bundle";
-    /** Plugin entry id, bundle package name, or installation spec, according to action. */
-    target?: string;
-    /** Required for set operations; defaults to true for installation. */
-    enabled?: boolean;
-    /** Zero-based list offset; defaults to 0. */
-    offset?: number;
-    /** List page size, from 1 to 100; defaults to 25. */
-    limit?: number;
-  } & Record<string, JsonValue>;
   /** Read a UTF-8 text file and return line-numbered content. */
   read: {
     /** Path to read, resolved by the filesystem backend. */
@@ -585,7 +572,6 @@ interface ToolOutputMap {
     parent?: string;
     depth?: number;
   })[];
-  plugin_manager: string;
   read: {
     path: string;
     offset: number;

+ 0 - 40
snapshots/session/cordis-inspect-jsdoc/tool-schemas.expected.json

@@ -457,46 +457,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/deepseek-messages-system-prompt/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/deepseek-protocol-system-prompt/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/foreground-confinement-timeout/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/fs-glob-sampling/tool-schemas.expected.json

@@ -180,46 +180,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/lsp-definition/tool-schemas.expected.json

@@ -297,46 +297,6 @@
         ]
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 14
snapshots/session/mcp-resources-ptc/system-prompt.expected.md

@@ -162,19 +162,6 @@ interface ToolArgsMap {
     /** Continuation cursor returned by this server. */
     cursor?: string;
   } & Record<string, JsonValue>;
-  /** List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart. */
-  plugin_manager: {
-    /** Management operation. */
-    action: "list_plugins" | "list_bundles" | "set_plugin" | "set_bundle" | "install_bundle" | "remove_bundle";
-    /** Plugin entry id, bundle package name, or installation spec, according to action. */
-    target?: string;
-    /** Required for set operations; defaults to true for installation. */
-    enabled?: boolean;
-    /** Zero-based list offset; defaults to 0. */
-    offset?: number;
-    /** List page size, from 1 to 100; defaults to 25. */
-    limit?: number;
-  } & Record<string, JsonValue>;
   /** Read a UTF-8 text file and return line-numbered content. */
   read: {
     /** Path to read, resolved by the filesystem backend. */
@@ -432,7 +419,6 @@ interface ToolOutputMap {
   })[];
   list_mcp_resource_templates: JsonValue;
   list_mcp_resources: JsonValue;
-  plugin_manager: string;
   read: {
     path: string;
     offset: number;

+ 0 - 40
snapshots/session/mcp-resources/tool-schemas.expected.json

@@ -300,46 +300,6 @@
         ]
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 3 - 0
snapshots/session/plugin-manager/cordis.snapshot.yml

@@ -51,3 +51,6 @@
                 reasoningEfforts: ['off', 'low', 'high', 'max']
                 defaultReasoningEffort: max
               - id: deepseek-v4-pro
+
+- id: tool-plugin-manager
+  disabled: false

+ 2 - 1
snapshots/session/plugin-manager/cordis.yml

@@ -1 +1,2 @@
-[]
+- id: tool-plugin-manager
+  disabled: false

+ 0 - 40
snapshots/session/product-subagent-both/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/product-subagent-codex/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/product-subagent-result-diagnostic/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/provider-cwd/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 14
snapshots/session/ptc-node-read-only/system-prompt.expected.md

@@ -140,19 +140,6 @@ interface ToolArgsMap {
     /** children (default) lists direct children only; descendants walks the complete tree below you. */
     scope?: "children" | "descendants";
   } & Record<string, JsonValue>;
-  /** List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart. */
-  plugin_manager: {
-    /** Management operation. */
-    action: "list_plugins" | "list_bundles" | "set_plugin" | "set_bundle" | "install_bundle" | "remove_bundle";
-    /** Plugin entry id, bundle package name, or installation spec, according to action. */
-    target?: string;
-    /** Required for set operations; defaults to true for installation. */
-    enabled?: boolean;
-    /** Zero-based list offset; defaults to 0. */
-    offset?: number;
-    /** List page size, from 1 to 100; defaults to 25. */
-    limit?: number;
-  } & Record<string, JsonValue>;
   /** Read a UTF-8 text file and return line-numbered content. */
   read: {
     /** Path to read, resolved by the filesystem backend. */
@@ -405,7 +392,6 @@ interface ToolOutputMap {
     parent?: string;
     depth?: number;
   })[];
-  plugin_manager: string;
   read: {
     path: string;
     offset: number;

+ 0 - 14
snapshots/session/ptc-node-workspace/system-prompt.expected.md

@@ -140,19 +140,6 @@ interface ToolArgsMap {
     /** children (default) lists direct children only; descendants walks the complete tree below you. */
     scope?: "children" | "descendants";
   } & Record<string, JsonValue>;
-  /** List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart. */
-  plugin_manager: {
-    /** Management operation. */
-    action: "list_plugins" | "list_bundles" | "set_plugin" | "set_bundle" | "install_bundle" | "remove_bundle";
-    /** Plugin entry id, bundle package name, or installation spec, according to action. */
-    target?: string;
-    /** Required for set operations; defaults to true for installation. */
-    enabled?: boolean;
-    /** Zero-based list offset; defaults to 0. */
-    offset?: number;
-    /** List page size, from 1 to 100; defaults to 25. */
-    limit?: number;
-  } & Record<string, JsonValue>;
   /** Read a UTF-8 text file and return line-numbered content. */
   read: {
     /** Path to read, resolved by the filesystem backend. */
@@ -405,7 +392,6 @@ interface ToolOutputMap {
     parent?: string;
     depth?: number;
   })[];
-  plugin_manager: string;
   read: {
     path: string;
     offset: number;

+ 0 - 15
snapshots/session/ptc-python-turn/system-prompt.expected.md

@@ -276,19 +276,6 @@ class ListAgentsOutput2(TypedDict):
     parent: NotRequired[str]
     depth: NotRequired[float]
 
-class PluginManagerArgs(TypedDict):
-    # Management operation.
-    action: Literal["list_plugins", "list_bundles", "set_plugin", "set_bundle", "install_bundle", "remove_bundle"]
-    # Plugin entry id, bundle package name, or installation spec, according to action.
-    target: NotRequired[str]
-    # Required for set operations; defaults to true for installation.
-    enabled: NotRequired[bool]
-    # Zero-based list offset; defaults to 0.
-    offset: NotRequired[float]
-    # List page size, from 1 to 100; defaults to 25.
-    limit: NotRequired[float]
-    # Additional keys beyond those declared are allowed.
-
 class ReadArgs(TypedDict):
     # Path to read, resolved by the filesystem backend.
     file_path: str
@@ -541,8 +528,6 @@ class Tools(Protocol):
         """Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap."""
     async def list_agents(self, args: ListAgentsArgs) -> list[ListAgentsOutput1 | ListAgentsOutput2]:
         """List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` steers a running child at its nearest step boundary or starts a turn for an idle or ready child, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only."""
-    async def plugin_manager(self, args: PluginManagerArgs) -> str:
-        """List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart."""
     async def read(self, args: ReadArgs) -> ReadOutput:
         """Read a UTF-8 text file and return line-numbered content."""
     async def read_image(self, args: ReadImageArgs) -> ReadImageOutput:

+ 0 - 14
snapshots/session/ptc-read-image/system-prompt.expected.md

@@ -140,19 +140,6 @@ interface ToolArgsMap {
     /** children (default) lists direct children only; descendants walks the complete tree below you. */
     scope?: "children" | "descendants";
   } & Record<string, JsonValue>;
-  /** List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart. */
-  plugin_manager: {
-    /** Management operation. */
-    action: "list_plugins" | "list_bundles" | "set_plugin" | "set_bundle" | "install_bundle" | "remove_bundle";
-    /** Plugin entry id, bundle package name, or installation spec, according to action. */
-    target?: string;
-    /** Required for set operations; defaults to true for installation. */
-    enabled?: boolean;
-    /** Zero-based list offset; defaults to 0. */
-    offset?: number;
-    /** List page size, from 1 to 100; defaults to 25. */
-    limit?: number;
-  } & Record<string, JsonValue>;
   /** Read a UTF-8 text file and return line-numbered content. */
   read: {
     /** Path to read, resolved by the filesystem backend. */
@@ -401,7 +388,6 @@ interface ToolOutputMap {
     parent?: string;
     depth?: number;
   })[];
-  plugin_manager: string;
   read: {
     path: string;
     offset: number;

+ 0 - 14
snapshots/session/ptc-turn/system-prompt.expected.md

@@ -140,19 +140,6 @@ interface ToolArgsMap {
     /** children (default) lists direct children only; descendants walks the complete tree below you. */
     scope?: "children" | "descendants";
   } & Record<string, JsonValue>;
-  /** List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart. */
-  plugin_manager: {
-    /** Management operation. */
-    action: "list_plugins" | "list_bundles" | "set_plugin" | "set_bundle" | "install_bundle" | "remove_bundle";
-    /** Plugin entry id, bundle package name, or installation spec, according to action. */
-    target?: string;
-    /** Required for set operations; defaults to true for installation. */
-    enabled?: boolean;
-    /** Zero-based list offset; defaults to 0. */
-    offset?: number;
-    /** List page size, from 1 to 100; defaults to 25. */
-    limit?: number;
-  } & Record<string, JsonValue>;
   /** Read a UTF-8 text file and return line-numbered content. */
   read: {
     /** Path to read, resolved by the filesystem backend. */
@@ -405,7 +392,6 @@ interface ToolOutputMap {
     parent?: string;
     depth?: number;
   })[];
-  plugin_manager: string;
   read: {
     path: string;
     offset: number;

+ 0 - 40
snapshots/session/pty-tools-sandbox-backend/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/ralph-loop/tool-schemas.1.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "ralph",
       "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",

+ 0 - 40
snapshots/session/ralph-loop/tool-schemas.2.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "ralph",
       "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",

+ 0 - 40
snapshots/session/ralph-loop/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "ralph",
       "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",

+ 0 - 40
snapshots/session/session-query-spill/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/subagent-acp-diagnostic/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/subagent-child-question-rejection/tool-schemas.expected.json

@@ -323,46 +323,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/subagent-tool-filter/tool-schemas.1.expected.json

@@ -174,46 +174,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/system-prompt-in-history/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/text-turn/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/web-fetch/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",

+ 0 - 40
snapshots/session/workflow-confinement/tool-schemas.expected.json

@@ -260,46 +260,6 @@
         }
       }
     },
-    {
-      "name": "plugin_manager",
-      "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "action": {
-            "type": "string",
-            "description": "Management operation.",
-            "enum": [
-              "list_plugins",
-              "list_bundles",
-              "set_plugin",
-              "set_bundle",
-              "install_bundle",
-              "remove_bundle"
-            ]
-          },
-          "target": {
-            "type": "string",
-            "description": "Plugin entry id, bundle package name, or installation spec, according to action."
-          },
-          "enabled": {
-            "type": "boolean",
-            "description": "Required for set operations; defaults to true for installation."
-          },
-          "offset": {
-            "type": "number",
-            "description": "Zero-based list offset; defaults to 0."
-          },
-          "limit": {
-            "type": "number",
-            "description": "List page size, from 1 to 100; defaults to 25."
-          }
-        },
-        "required": [
-          "action"
-        ]
-      }
-    },
     {
       "name": "read",
       "description": "Read a UTF-8 text file and return line-numbered content.",