dsh-github-copilot 0.4.0-alpha.22 → 0.4.0-alpha.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0-alpha.23 (prepared)
4
+
5
+ - Fix Model roles loading by exposing the dedicated Host `view`, `save` and `create` methods through the public Typert Remote service, retaining strict validation, CAS and session-creation ownership (#134).
6
+ - Replace Copilot-specific Auto/fixed labels and static provider suggestions with a real registered search-provider catalog shared by the primary and final-fallback selectors (#135).
7
+ - Follow the initiating Chat provider in Auto mode, keep an explicitly selected primary independent of Chat, and attempt at most one distinct final fallback. Preserve legacy settings without automatic writes and keep model choices provider-owned.
8
+ - Capture registration and account continuity before asynchronous work; silently revoked Copilot proof, cancellation, unload or registration replacement cannot authorize a paid fallback.
9
+ - Add real Host Gateway, registration-lifecycle, provider-routing and UI regressions plus an isolated built-component browser fixture. Preserve alpha.21 shared-peer ownership and alpha.22 React Client external declarations; no Core or live-profile changes are included.
10
+
3
11
  ## 0.4.0-alpha.22 (prepared)
4
12
 
5
13
  - Declare React as the DSH Client ModuleLoader external used by the actual built Client bundle, while removing it from the strict Desktop Node peer graph and retaining it only for development.
package/README.md CHANGED
@@ -52,7 +52,7 @@ The built Client intentionally requests React from DSH's browser `ModuleLoader`
52
52
 
53
53
  ## Install and sign in
54
54
 
55
- The commands below target the package version `0.4.0-alpha.22`. Versioned URLs describe the intended release artifacts, not proof that publication or local activation has completed; use them only once that Release and its checksums are available. Install into the profile you use (replace `web` when targeting another profile):
55
+ The commands below target the package version `0.4.0-alpha.23`. Versioned URLs describe the intended release artifacts, not proof that publication or local activation has completed; use them only once that Release and its checksums are available. Install into the profile you use (replace `web` when targeting another profile):
56
56
 
57
57
  Before installing/updating, unpack the **checksum-verified** archive into a temporary directory and run its read-only composition preflight (replace all paths with absolute paths for the intended profile):
58
58
 
@@ -65,7 +65,7 @@ Include any launcher patch files with repeated `--patch /absolute/file` argument
65
65
  For approved online installation, the supported CLI command is:
66
66
 
67
67
  ```sh
68
- dsh plugin --profile web add https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.22/dsh-github-copilot-0.4.0-alpha.22.tgz
68
+ dsh plugin --profile web add https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.23/dsh-github-copilot-0.4.0-alpha.23.tgz
69
69
  ```
70
70
 
71
71
  If registry access is blocked or unavailable, do not retry it through another network. Desktop-managed profiles may instead use the [controlled offline CLI procedure](./docs/npm-distribution.md#controlled-offline-cli-maintenance), with an approved, checksum-verified local Release and existing dependency cache (`--offline --ignore-scripts`). All preflight and approval requirements still apply.
@@ -130,7 +130,7 @@ Agents should treat the browser authorization as a human handoff, not as a token
130
130
  5. Confirm **Signed in** and inspect the automatic discovery result before asking the user to choose a model. Already-signed-in Models opening ensures missing/stale metadata automatically; fresh ready cache makes no request. Use visible **Retry** for errors or **Manage → Refresh models** for an intentional forced update, not routine setup. Status alone does not discover, and login, metadata and successful model calls remain separate evidence.
131
131
  6. Use **Sign out** only when the user explicitly asks to disconnect the account. It deletes the Copilot credential record but preserves route settings.
132
132
 
133
- GitHub Releases and npm are the default distribution channels for each new version, using the same verified tarball. Pin the version and verify the evidence for the channel used: Release `SHA256SUMS`, and npm `dist.integrity` when installing from npm. The native Desktop package manager is preferred when permitted registry access is available; after npm publication is verified it accepts `dsh-github-copilot@0.4.0-alpha.22`, not a URL or file. Desktop-managed profiles also support explicitly approved [controlled offline CLI maintenance](./docs/npm-distribution.md#controlled-offline-cli-maintenance) with a verified local Release and `--offline --ignore-scripts`. Follow the mandatory search-composition preflight, backup, single-writer and post-install checks; do not bypass a corporate registry ban, disable TLS verification or restart without separate approval. Offline installation is not proof that npm networking/publication was repaired. See [distribution and publication requirements](./docs/npm-distribution.md).
133
+ GitHub Releases and npm are the default distribution channels for each new version, using the same verified tarball. Pin the version and verify the evidence for the channel used: Release `SHA256SUMS`, and npm `dist.integrity` when installing from npm. The native Desktop package manager is preferred when permitted registry access is available; after npm publication is verified it accepts `dsh-github-copilot@0.4.0-alpha.23`, not a URL or file. Desktop-managed profiles also support explicitly approved [controlled offline CLI maintenance](./docs/npm-distribution.md#controlled-offline-cli-maintenance) with a verified local Release and `--offline --ignore-scripts`. Follow the mandatory search-composition preflight, backup, single-writer and post-install checks; do not bypass a corporate registry ban, disable TLS verification or restart without separate approval. Offline installation is not proof that npm networking/publication was repaired. See [distribution and publication requirements](./docs/npm-distribution.md).
134
134
 
135
135
  No `copilot2api` process, external gateway, placeholder API key, pasted GitHub token, or separate `dsh-web-search-provider` installation is required.
136
136
 
@@ -149,7 +149,9 @@ DSH Core continues to own model selection, sandboxing, tools, attachments, and o
149
149
 
150
150
  Under **Settings → Models → Model roles**, enable dedicated dual-model sessions, select two available account models, save, and choose **Create session with this configuration** for an existing workspace. The planner handles planning and acceptance; `copilot_execute` delegates implementation to a native continuable child with a fixed execution model. Configuration is off by default and affects only sessions created through this entry. It does not change the global default, existing sessions, credentials or the ordinary Subagent model-selection setting.
151
151
 
152
- Unavailable models are not substituted. Uncertain creation retries keep the same request identity. The feature requires public role/session/subagent capabilities and is visibly unavailable when they are absent; historical package compatibility is not blanket certification of this optional flow. See [setup, lifecycle, limitations and evidence](./docs/dual-model.md). The feature is included in the `0.4.0-alpha.22` candidate; source and fixture tests are not proof of publication or Desktop activation.
152
+ Unavailable models are not substituted. Uncertain creation retries keep the same request identity. The feature requires public role/session/subagent capabilities and is visibly unavailable when they are absent; historical package compatibility is not blanket certification of this optional flow. See [setup, lifecycle, limitations and evidence](./docs/dual-model.md). The feature is included in the `0.4.0-alpha.23` candidate; source and fixture tests are not proof of publication or Desktop activation.
153
+
154
+ If the card says **Could not load model roles**, do not change model defaults to work around it: this is a failed settings load, distinct from unsupported capabilities or unavailable models. In particular, a `githubCopilotDualModel/view` HTTP 404 indicates missing Host Remote exposure, not that the feature is off. See [troubleshooting and verification](./docs/dual-model.md#loading-and-remote-troubleshooting).
153
155
 
154
156
  ## Shared account, independent sessions (V3)
155
157
 
@@ -228,22 +230,22 @@ The bundle composes a plugin-owned web-service facade while preserving the origi
228
230
 
229
231
  A separate **Web search** card under **Settings → Models** controls routing across search backends. On older Core versions without the Models footer, it appears as **Settings → Web search**. The companion owns the namespaced policy (`github-copilot-search-routing`); it does not claim a global Core namespace. Removing the companion restores the original web service.
230
232
 
231
- Auto detection currently recognizes the companion's Copilot routes only. Other providers can be selected explicitly as the default or fixed backend; generic WebSearchProvider registration does not establish an association with a chat provider.
233
+ The **Search provider** selector offers **Auto follow Chat** and the actual search providers registered through the routed facade. **Default search provider** uses the same catalog, with an additional **None no fallback** choice. These are search backends, not individual models: Copilot is one backend even when different account models can execute its search requests.
232
234
 
233
- - `github-copilot-search-routing.searchMode: auto` prefers eligible Copilot native search for the initiating chat model. Other chat routes use `defaultSearchProvider`.
234
- - `github-copilot-search-routing.searchMode: fixed` always uses `defaultSearchProvider`, independently of the chat model.
235
- - `github-copilot-search-routing.defaultSearchProvider` is a registered search-provider id such as `deepseek-official`, `github-copilot-hosted`, `exa` or `perplexity`; `none` disables the default/fixed search path without affecting chat.
236
- - When `github-copilot-hosted` serves independently of the chat model, `github-copilot.searchModel` names the account-authorized OpenAI Responses model used for the auxiliary search request.
235
+ - `github-copilot-search-routing.searchProvider: auto` follows the initiating Chat provider. Copilot's plugin-owned aliases retain their existing ownership and model-capability checks; other Chat provider IDs must exactly match a registered search provider ID. Names, suffixes and model families are never guessed. This convention does not promise the same model or account across independently registered providers.
236
+ - A concrete `searchProvider` ID pins the primary backend independently of Chat.
237
+ - `defaultSearchProvider` is only the final fallback when no primary matches or the primary search fails. It is attempted at most once, never retried when it is already the primary, and never replaces a successful empty result. `none` disables fallback without disabling the primary.
238
+ - Copilot selected explicitly or as the fallback uses its provider-owned `github-copilot.searchModel`. Other backends own their model configuration, if any. A registered backend is not proof that every model supports search.
237
239
 
238
- Copilot chat Sessions continue to prefer their selected model's native hosted search in `auto` mode under the existing account/protocol/probe gates. A Volcengine or other non-Copilot Session can instead use an explicitly configured Copilot search model; chat and search remain separate requests. Selecting another registered provider dispatches directly to that provider through the routed facade. Fetch is unchanged.
240
+ Existing `searchMode: auto/fixed` settings remain readable without automatic writes. Legacy fixed mode preserves its old default as the primary; fixed plus `none` stays disabled. An explicit save writes the new independent keys. The UI explains that saving adopts the chosen final fallback, including possible API charges; legacy `github-copilot.searchFallback: none` failure-spending restrictions remain until that choice is saved. Unregistered saved IDs remain visible as unavailable instead of being silently substituted.
239
241
 
240
- `github-copilot.routeWebSearch` remains the compatibility switch: setting it to `false` restores the original configured web service unchanged. `github-copilot.searchFallback` (`deepseek` by default, or `none`) governs a native Copilot failure only when `deepseek-official` is also the selected default; choosing another default never triggers a hidden DeepSeek request. Copilot sign-in does not supply a DeepSeek key; selecting or falling back to `deepseek-official` requires separately configured DeepSeek credentials and may incur DeepSeek API charges.
242
+ `github-copilot.routeWebSearch: false` still delegates to the original configured web service. Otherwise cancellation, unload and captured account-proof invalidation never authorize a fallback. Generic registered backends must honor cancellation; their public interface does not expose an internal pre-network authorization hook. The historical direct Copilot/DeepSeek path retains its stronger owned pre-dispatch guard. Neither path supplies another provider's credentials from Copilot sign-in.
241
243
 
242
- There is no need to set the profile-wide `web.searchProvider` to Copilot. The Models-page router selects exact registered providers per search, while an old manual global `github-copilot-hosted` override can still break the preserved original path and should be removed. Official DSH also provides separate `exa` and `perplexity` search-provider packages; they appear only when installed, mounted and credentialed. Community providers can participate through the same public registration seam. Custom/nonstandard web-service compositions still need review because the bundle expects the stock official `web` row. See [implementation and evidence limits](docs/session-search-routing.md).
244
+ The catalog contains registrations observed by this facade, not hidden registrations made directly in another realm. Listing it does not call availability checks, model discovery, credentials or search; actual usability is checked for each request. No static list of example providers is presented as installed support. Custom/nonstandard web compositions still require review. Fetch is unchanged. See [implementation and evidence limits](docs/session-search-routing.md).
243
245
 
244
246
  Requests go directly to the credential-resolved HTTPS Copilot endpoint after strict host validation: GitHub-hosted `api.*.githubcopilot.com`, or `copilot-api.<signed-in-enterprise-domain>` for an accepted GitHub Enterprise credential. No external gateway receives the credential.
245
247
 
246
- By default (`probe: true`), search fails closed unless the selected route is canonical `github-copilot` or the plugin-owned `github-copilot-preview`, the account authorizes the model, its verified protocol supports native search, and a bounded capability probe succeeds. Managed-model conversations always use their native adapter; independent Responses `ctx.web` search uses account-bound authorization without the old static-catalog ID restriction. Setting `probe: false` bypasses only capability proof and trusts the selected native protocol; route, account, protocol, endpoint, and authentication checks remain active. The underlying hosted-search provider does not itself fall back. The new session router may apply the explicitly disclosed DeepSeek fallback policy for eligible failures; aborts and invalidated owner/account proofs remain terminal. Requests containing any Core file block—including files nested in tool-result content—also fail closed to `next()`, preserving Core's file projection instead of letting the hosted-search serializer drop that context.
248
+ By default (`probe: true`), search fails closed unless the selected route is canonical `github-copilot` or the plugin-owned `github-copilot-preview`, the account authorizes the model, its verified protocol supports native search, and a bounded capability probe succeeds. Managed-model conversations always use their native adapter; independent Responses `ctx.web` search uses account-bound authorization without the old static-catalog ID restriction. Setting `probe: false` bypasses only capability proof and trusts the selected native protocol; route, account, protocol, endpoint, and authentication checks remain active. The underlying hosted-search provider does not itself fall back. The routed facade may apply the explicitly selected final fallback for eligible failures; aborts and invalidated owner/account proofs remain terminal. Requests containing any Core file block—including files nested in tool-result content—also fail closed to `next()`, preserving Core's file projection instead of letting the hosted-search serializer drop that context.
247
249
 
248
250
  Search proof is lazy: attach, settings updates and `credentials/record-updated` for `llm-pi-ai/github-copilot` only invalidate cached plans, without starting network work. The next actual eligible request proves capability again; unrelated credentials are ignored and event bursts do not trigger repeated eager probes. In-flight proofs are cancelled on invalidation/disposal. If credentials change during proof or final auth resolution, the current request fails closed rather than applying account A's proof to account B. Submit a new request after the update; there is no automatic retry loop or implicit `probe: false` fallback.
249
251
 
@@ -372,8 +374,8 @@ Report the published Release URL, version, tag/commit and verified asset SHA-256
372
374
  `package.json` declares public npm distribution. A release tag must equal `v${package.json.version}`. Versions use standard SemVer prerelease labels (`alpha`, `beta`, or `rc`), each with its matching npm dist-tag; only stable versions use `latest`. The Release workflow performs the frozen install and complete verification gate, packs once (or recovers the original archive on retry), verifies `SHA256SUMS`, publishes the immutable GitHub Release and then publishes those same bytes to npm through OIDC. Either channel failing means delivery is incomplete. First package creation needs an authorized maintainer; staging requires an existing package and is not a first-package bootstrap. Historical releases are not republished.
373
375
 
374
376
  ```sh
375
- curl -LO https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.22/dsh-github-copilot-0.4.0-alpha.22.tgz
376
- curl -LO https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.22/SHA256SUMS
377
+ curl -LO https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.23/dsh-github-copilot-0.4.0-alpha.23.tgz
378
+ curl -LO https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.23/SHA256SUMS
377
379
  sha256sum --check SHA256SUMS
378
380
  ```
379
381
 
@@ -381,7 +383,7 @@ PowerShell can verify the same two downloaded files with:
381
383
 
382
384
  ```powershell
383
385
  $expected = (Get-Content .\SHA256SUMS).Split()[0]
384
- $actual = (Get-FileHash .\dsh-github-copilot-0.4.0-alpha.22.tgz -Algorithm SHA256).Hash.ToLowerInvariant()
386
+ $actual = (Get-FileHash .\dsh-github-copilot-0.4.0-alpha.23.tgz -Algorithm SHA256).Hash.ToLowerInvariant()
385
387
  if ($actual -cne $expected) { throw 'Release checksum mismatch' }
386
388
  ```
387
389
 
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  一个聚焦 GitHub Copilot 登录、通用账号模型发现、Copilot 专用 Tool 兼容与供应方托管搜索的 DSH companion。插件根据供应方返回的端点和能力元数据组装模型,复用公开的 `@deepseek-ai/dsh-llm-pi-ai` adapter 与 pi-ai SDK,不另写一套通用传输/序列化器,也不维护需要逐个添加新模型 ID 的静态目录。
11
11
 
12
- > 下文自动维护账号模型元数据与 provider 集成控件描述目标版本 `0.4.0-alpha.22`;这不代表已有的两条真实路由被合并或移除。版本化 URL 不表示 Release 已发布或本机已加载;仅在该 Release 与校验和可用后使用安装命令。源码、发布制品、已安装版本和实际加载运行时需分别确认,本地升级和中断会话的重启仍需用户批准。
12
+ > 下文自动维护账号模型元数据与 provider 集成控件描述目标版本 `0.4.0-alpha.23`;这不代表已有的两条真实路由被合并或移除。版本化 URL 不表示 Release 已发布或本机已加载;仅在该 Release 与校验和可用后使用安装命令。源码、发布制品、已安装版本和实际加载运行时需分别确认,本地升级和中断会话的重启仍需用户批准。
13
13
 
14
14
  ## 已测试基线
15
15
 
@@ -67,7 +67,7 @@ node package/scripts/check-search-composition.mjs --profile-dir /absolute/profil
67
67
  获准且网络可用时,可通过受支持的 CLI 命令安装:
68
68
 
69
69
  ```sh
70
- dsh plugin --profile web add https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.22/dsh-github-copilot-0.4.0-alpha.22.tgz
70
+ dsh plugin --profile web add https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.23/dsh-github-copilot-0.4.0-alpha.23.tgz
71
71
  ```
72
72
 
73
73
  若 registry 被公司封禁或不可用,不要更换网络绕行。Desktop 管理的 profile 可以改用[受控离线 CLI 流程](./docs/npm-distribution.md#controlled-offline-cli-maintenance):使用来源获准、校验通过的本地 Release 和已有依赖缓存(`--offline --ignore-scripts`)。预检、备份和授权要求仍然适用。
@@ -132,7 +132,7 @@ Agent 应把浏览器授权视为需要用户完成的 handoff,而不是自行
132
132
  5. 确认 **Signed in** 并检查自动发现结果,再请用户选择模型。已登录时打开 Models 会自动确保缺失/过期元数据,新鲜 ready 缓存不发请求。错误可使用 **Retry**,有意强制更新时使用 **Manage → Refresh models**,不作为常规设置步骤。状态读取本身不发现;登录、元数据与真实调用成功是独立证据。
133
133
  6. 只有用户明确要求断开账号时才使用 **Sign out**;它会删除 Copilot credential record,但保留 route settings。
134
134
 
135
- 每个新版本默认同时分发到 GitHub Releases 和 npm,两个渠道使用同一份已验证 tarball。应固定版本并核对所用渠道的证据:Release 的 `SHA256SUMS`;从 npm 安装时另核对 `dist.integrity`。在获准且可用的 registry 网络环境中,优先使用原生 Desktop 包管理器;确认 npm 发布后,它接受 `dsh-github-copilot@0.4.0-alpha.22`,不是 URL 或本地文件。Desktop 管理的 profile 也允许经明确授权的[受控离线 CLI 维护](./docs/npm-distribution.md#controlled-offline-cli-maintenance):使用已验证的本地 Release 和 `--offline --ignore-scripts`,执行必需的组合预检、私密元数据备份、单写入者控制及安装后差异核验。缓存不足或出现权限拒绝时停止,不绕过公司 registry 封禁,不关闭 TLS 校验;重启仍需单独授权。离线安装成功不表示 npm 联网或发布问题已修好。[双渠道发布与 OIDC 要求](./docs/npm-distribution.md)保持不变。
135
+ 每个新版本默认同时分发到 GitHub Releases 和 npm,两个渠道使用同一份已验证 tarball。应固定版本并核对所用渠道的证据:Release 的 `SHA256SUMS`;从 npm 安装时另核对 `dist.integrity`。在获准且可用的 registry 网络环境中,优先使用原生 Desktop 包管理器;确认 npm 发布后,它接受 `dsh-github-copilot@0.4.0-alpha.23`,不是 URL 或本地文件。Desktop 管理的 profile 也允许经明确授权的[受控离线 CLI 维护](./docs/npm-distribution.md#controlled-offline-cli-maintenance):使用已验证的本地 Release 和 `--offline --ignore-scripts`,执行必需的组合预检、私密元数据备份、单写入者控制及安装后差异核验。缓存不足或出现权限拒绝时停止,不绕过公司 registry 封禁,不关闭 TLS 校验;重启仍需单独授权。离线安装成功不表示 npm 联网或发布问题已修好。[双渠道发布与 OIDC 要求](./docs/npm-distribution.md)保持不变。
136
136
 
137
137
  不需要运行 `copilot2api`,不需要外部 gateway、placeholder API key、原始 GitHub token 或单独安装 `dsh-web-search-provider`。
138
138
 
@@ -153,7 +153,9 @@ DSH Core 继续负责模型选择、sandbox、工具、附件与其它 provider
153
153
 
154
154
  在 **设置 → 模型 → 模型分工** 中启用双模型会话,选择账号下可用的主模型和执行模型,保存后选工作区,再点 **用此配置新建会话**。主模型负责规划与验收;`copilot_execute` 创建原生可继续执行的子代理,并固定其执行模型。默认关闭,仅专用入口创建的新会话采用此策略;不修改全局默认、已有会话、登录凭据或原生 Subagent 授权开关。
155
155
 
156
- 模型不可用时明确报错,不自动替换。创建结果不明时重试同一个请求,不为绕过未知结果另建会话。功能依赖公开的会话、策略和子代理能力;缺少接口时显示不可用,不把历史版本的包兼容范围当成此功能的全面验收。详见[配置、生命周期、限制与验证范围](./docs/dual-model.md)。本功能包含在 `0.4.0-alpha.22` candidate 中,源码和合成测试不代表已发布或当前 Desktop 已生效。
156
+ 模型不可用时明确报错,不自动替换。创建结果不明时重试同一个请求,不为绕过未知结果另建会话。功能依赖公开的会话、策略和子代理能力;缺少接口时显示不可用,不把历史版本的包兼容范围当成此功能的全面验收。详见[配置、生命周期、限制与验证范围](./docs/dual-model.md)。本功能包含在 `0.4.0-alpha.23` candidate 中,源码和合成测试不代表已发布或当前 Desktop 已生效。
157
+
158
+ 若卡片显示 **Could not load model roles(无法加载模型分工)**,不要通过修改默认模型绕过:这是设置加载失败,与能力不支持或模型不可用不同。尤其 `githubCopilotDualModel/view` 返回 HTTP 404,表示 Host Remote 未暴露,并非功能开关处于关闭状态。详见[加载故障排查与验证](./docs/dual-model.md#loading-and-remote-troubleshooting)。
157
159
 
158
160
  ## 全局账号,多模型与独立会话(V3)
159
161
 
@@ -236,22 +238,22 @@ Grant 写入或复用前,Host normalizer 只会把 pi-ai 文档化的 `type`
236
238
 
237
239
  **设置 → 模型**下新增独立的 **Web search** 卡片;旧版 Core 没有 Models footer 时,使用独立的 **设置 → Web search** 分区。策略由本插件的 `github-copilot-search-routing` 命名空间持有,不占用 Core 通用命名空间;卸载本插件后恢复原 web 服务。
238
240
 
239
- 当前 Auto 自动关联只识别本插件的 Copilot 聊天路由。其他搜索 Provider 可显式选为默认/固定后端;仅注册 WebSearchProvider 不能证明它与某个聊天 Provider 的关联。
241
+ **Search provider** 主选择器提供 **Auto follow Chat** 和通过路由外观层实际注册的搜索 Provider;**Default search provider** 使用同一目录,额外提供 **None — no fallback**。选择项代表搜索后端,不代表单个模型:即使多个账号模型可以执行搜索,Copilot 仍然只有一个搜索后端。
240
242
 
241
- - `github-copilot-search-routing.searchMode: auto` 优先使用发起会话的合格 Copilot 原生搜索;其他聊天路由使用 `defaultSearchProvider`。
242
- - `github-copilot-search-routing.searchMode: fixed` 忽略聊天模型,始终使用 `defaultSearchProvider`。
243
- - `github-copilot-search-routing.defaultSearchProvider` 是已注册的搜索提供方 id,例如 `deepseek-official`、`github-copilot-hosted`、`exa` 或 `perplexity`;设为 `none` 只禁用默认/固定搜索,不影响聊天。
244
- - `github-copilot-hosted` 独立于聊天模型提供搜索时,`github-copilot.searchModel` 指定用于辅助搜索请求、且账号已授权的 OpenAI Responses 模型。
243
+ - `github-copilot-search-routing.searchProvider: auto` 跟随发起 Chat 的 Provider。本插件自有 Copilot 别名保留既有 owner 和模型能力检查;其他 Chat Provider 的原始 ID 必须精确匹配已注册的搜索 Provider ID,不猜名字、后缀或模型家族。这是路由约定,不承诺独立注册的后端一定使用相同模型或账号。
244
+ - `searchProvider` 设为具体 ID,就固定主搜索后端,不再随 Chat 改变。
245
+ - `defaultSearchProvider` 仅在无匹配主后端或主搜索失败时作最终兜底,最多尝试一次;与主后端相同时不重试,成功但结果为空也不触发兜底。`none` 仅关闭兜底,不关闭主搜索。
246
+ - 显式选择 Copilot 或将它作为兜底时,使用 Provider 自有的 `github-copilot.searchModel`。其他后端自行管理模型配置(若有);后端已注册不代表其每个模型都支持搜索。
245
247
 
246
- `auto` 模式下,Copilot 聊天会话仍优先使用当前所选模型的原生托管搜索,并保留原有账号/协议/probe 检查。火山方舟或其他非 Copilot 会话可以改用显式配置的 Copilot 搜索模型;聊天与搜索是两次独立请求。选择其他已注册提供方时,路由外观层按 id 直接调用该提供方。网页抓取不变。
248
+ `searchMode: auto/fixed` 配置继续兼容读取,不自动写入。旧 fixed 保留原 default 作为主后端;fixed `none` 继续禁用。用户明确保存后才写入新的独立配置键。界面会说明保存将采用选中的最终兜底并可能产生 API 费用;旧的 `github-copilot.searchFallback: none` 失败兜底付费限制保留到明确保存新选择为止。已保存但未注册的 ID 保持显示为不可用,不静默替换。
247
249
 
248
- `github-copilot.routeWebSearch` 保留为兼容开关:设为 `false` 时恢复原始 web 服务配置。`github-copilot.searchFallback`(默认 `deepseek`;或设为 `none`)仅在默认 Provider 也选为 `deepseek-official` 时控制原生 Copilot 失败后的回退;选择其他默认 Provider 不会暗中请求 DeepSeek。Copilot 登录不会提供 DeepSeek Key;选择或回退到 `deepseek-official` 都需要单独配置 DeepSeek 凭据,并可能产生 DeepSeek API 费用。
250
+ `github-copilot.routeWebSearch: false` 仍委托原始 web 服务。其余情况下,取消、卸载及捕获的账号证明失效都不能触发兜底。通用注册后端必须遵守取消信号,但公开接口没有提供其内部鉴权后、网络发送前的检查钩子;历史 Copilot/DeepSeek 直调路径保留更强的自有发送前保护。两条路径都不会用 Copilot 登录替其他 Provider 提供凭据。
249
251
 
250
- 无需把全局 `web.searchProvider` 改成 Copilot。Models 页路由器会为每次搜索按 id 选择已注册 Provider;旧的全局 `github-copilot-hosted` 手工 override 仍可能破坏保留的原始路径,应当删除。DSH 官方还提供独立的 `exa` 与 `perplexity` 搜索 Provider 包;只有安装、挂载并配置凭据后才会可用。社区 Provider 也可以通过同一个公开注册 seam 接入。bundle 仍预期标准官方 `web` 行,自定义或非标准 web 服务组合需要单独审查。详见[实现与验收范围](docs/session-search-routing.md)。
252
+ 目录只包含本路由外观层观察到的注册,不包含直接在其他作用域注册的隐藏后端。列目录不做可用性探测、模型发现、凭据读取或搜索;实际调用时再检查可用性,不再把写死的示例列表当成已安装支持。非标准 web 组合仍需审查,网页抓取不变。详见[实现与验收范围](docs/session-search-routing.md)。
251
253
 
252
254
  请求经过严格 Host 校验后,直接发往 credential 解析出的 HTTPS Copilot endpoint:GitHub-hosted `api.*.githubcopilot.com`,或已接受 GitHub Enterprise credential 对应的 `copilot-api.<signed-in-enterprise-domain>`。Credential 不会经过外部 gateway。
253
255
 
254
- 默认 `probe: true` 时,搜索 fail closed:当前 route 必须是 canonical Copilot 或本插件拥有的托管账号路由,账号必须允许该模型,所选协议必须支持对应搜索表面,且 bounded capability probe 必须成功。托管模型还必须有当前账号的有效发现证据,不能拿另一个 pi 副本的静态条目代替。显式设置 `probe: false` 只会跳过 capability proof,并信任所选原生协议;route、account、protocol、endpoint 与 authentication 检查仍然生效。底层 hosted-search provider 自身不执行回退;新的会话分流层可按明确披露的策略对符合条件的失败执行 DeepSeek 回退,但取消和 owner/账号证明失效仍立即终止。请求只要包含任意 Core file block(包括嵌套在 tool-result content 内的文件),也会 fail closed 到 `next()`,由 Core 保留文件投影,避免 hosted-search serializer 静默丢弃文件上下文。
256
+ 默认 `probe: true` 时,搜索 fail closed:当前 route 必须是 canonical Copilot 或本插件拥有的托管账号路由,账号必须允许该模型,所选协议必须支持对应搜索表面,且 bounded capability probe 必须成功。托管模型还必须有当前账号的有效发现证据,不能拿另一个 pi 副本的静态条目代替。显式设置 `probe: false` 只会跳过 capability proof,并信任所选原生协议;route、account、protocol、endpoint 与 authentication 检查仍然生效。底层 hosted-search provider 自身不执行回退;路由外观层可对符合条件的失败使用显式选择的最终兜底后端,但取消和 owner/账号证明失效仍立即终止。请求只要包含任意 Core file block(包括嵌套在 tool-result content 内的文件),也会 fail closed 到 `next()`,由 Core 保留文件投影,避免 hosted-search serializer 静默丢弃文件上下文。
255
257
 
256
258
  搜索 proof 采用惰性验证:attach、settings 更新以及 `llm-pi-ai/github-copilot` 的 `credentials/record-updated` 事件只使缓存计划失效,不启动网络工作。下一次真实且符合条件的请求才重新验证;忽略无关凭据更新,连续事件不会引发重复的提前 probe。失效或卸载会取消正在执行的 proof。如果凭据在 proof 或最终认证解析期间改变,当前请求会 fail closed,避免把账号 A 的 proof 用于账号 B。更新后可重新提交请求;不会自动循环重试,也不会隐式使用 `probe: false`。
257
259
 
@@ -383,8 +385,8 @@ node scripts/agent.mjs attribution "DeepSeek Harness (DSH)"
383
385
  `package.json` 声明公开 npm 分发。Release tag 必须严格等于 `v${package.json.version}`。预发布使用 `alpha`、`beta` 或 `rc` 及对应 npm dist-tag,只有稳定版使用 `latest`。Release workflow 执行 frozen install 和完整门禁,只打包一次(重试恢复原始归档),验证 `SHA256SUMS`,发布不可变 GitHub Release,再通过 OIDC 将同一份字节发布到 npm。任一渠道失败都表示交付未完成。首次建包须由获准环境中的维护者完成;staging 要求包已存在,不能代替首次建包。不会批量补发历史版本。
384
386
 
385
387
  ```sh
386
- curl -LO https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.22/dsh-github-copilot-0.4.0-alpha.22.tgz
387
- curl -LO https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.22/SHA256SUMS
388
+ curl -LO https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.23/dsh-github-copilot-0.4.0-alpha.23.tgz
389
+ curl -LO https://github.com/cloga/dsh-github-copilot/releases/download/v0.4.0-alpha.23/SHA256SUMS
388
390
  sha256sum --check SHA256SUMS
389
391
  ```
390
392
 
@@ -392,7 +394,7 @@ PowerShell 可以对已下载的同一组文件执行:
392
394
 
393
395
  ```powershell
394
396
  $expected = (Get-Content .\SHA256SUMS).Split()[0]
395
- $actual = (Get-FileHash .\dsh-github-copilot-0.4.0-alpha.22.tgz -Algorithm SHA256).Hash.ToLowerInvariant()
397
+ $actual = (Get-FileHash .\dsh-github-copilot-0.4.0-alpha.23.tgz -Algorithm SHA256).Hash.ToLowerInvariant()
396
398
  if ($actual -cne $expected) { throw 'Release checksum mismatch' }
397
399
  ```
398
400
 
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "package": {
10
10
  "name": "dsh-github-copilot",
11
- "version": "0.4.0-alpha.22"
11
+ "version": "0.4.0-alpha.23"
12
12
  },
13
13
  "evidence": {
14
14
  "kind": "source-and-synthetic-test-inventory",
@@ -234,8 +234,12 @@
234
234
  {
235
235
  "id": "session-model-search-routing",
236
236
  "required": true,
237
- "evidenceScope": "development-real-consumer-and-loader-synthetic",
237
+ "evidenceScope": "development-real-consumer-loader-gateway-and-ui-synthetic",
238
238
  "sourceMarkers": [
239
+ {"file": "src/search-routing-host.ts", "marker": "extends TypertRemoteService"},
240
+ {"file": "src/search-routing-remote.ts", "marker": "githubCopilotSearchRouting.providers"},
241
+ {"file": "src/search-routing-policy.ts", "marker": "normalizeWebSearchRouting"},
242
+ {"file": "src/search-tool-routing.ts", "marker": "routeSearchTools"},
239
243
  {"file": "src/routed-web.ts", "marker": "extends WebRuntime"},
240
244
  {"file": "src/index.ts", "marker": "ctx.provide('githubCopilotSearchRouter'"},
241
245
  {"file": "src/web-search-routing-config.ts", "marker": "WEB_SEARCH_ROUTING_SETTINGS_NAMESPACE"},
@@ -243,6 +247,9 @@
243
247
  {"file": "cordis.patch.yml", "marker": "web: github-copilot-original-web"}
244
248
  ],
245
249
  "tests": [
250
+ {"file": "tests/search-provider-catalog.spec.ts", "name": "lists only actual facade registrations without availability checks, auth or search"},
251
+ {"file": "tests/scripts/search-provider-gateway.test.mjs", "name": "search catalog Remote reaches actual facade registrations and unloads cleanly"},
252
+ {"file": "tests/web-search-routing-card.spec.ts", "name": "loads one actual provider catalog into both selectors without Copilot-specific Auto labels"},
246
253
  {"file": "tests/routed-web.spec.ts", "name": "keeps the configured provider for DeepSeek and all other model routes"},
247
254
  {"file": "tests/routed-web.spec.ts", "name": "dispatches a router-selected provider by exact registered id"},
248
255
  {"file": "tests/routed-web.spec.ts", "name": "retains pre-execute denial and the surrounding execution middleware"},
@@ -30,6 +30,14 @@ The browser generates a request UUID and keeps the original workspace and settin
30
30
 
31
31
  After a confirmed save conflict, reload settings and review the current configuration. During an uncertain creation the original input is held until confirmation. Reloading the whole browser can lose its in-memory pending receipt; inspect the session list before issuing a fresh creation request. The same supplied request UUID remains idempotent on the Host across process restart.
32
32
 
33
+ ## Loading and Remote troubleshooting
34
+
35
+ **Could not load model roles** is a failed Remote load, not an instruction to change global model defaults. A `githubCopilotDualModel/view` HTTP 404 means the Host did not expose the endpoint; Client descriptors alone cannot register Host methods. The Host uses the public `TypertRemoteService` binding and marks only `view`, `save` and `create` with `@Remote`. A source fix does not change an already running installation.
36
+
37
+ A successful view can still report `DUAL_MODEL_UNSUPPORTED` when optional public capabilities are missing. An off configuration alone does not disable the model selectors. Read-only settings, a missing revision, pending operations, unavailable models and unavailable workspaces retain their existing gates. Reload after an ordinary transient failure; do not repeatedly save or create sessions to diagnose an endpoint failure.
38
+
39
+ Loading a supported view performs non-forcing account discovery and may use the existing OAuth refresh/network lifecycle. It is not guaranteed to be a credential- or network-side-effect-free probe. Regression tests use isolated synthetic services instead of the live profile.
40
+
33
41
  ## UI verification captures
34
42
 
35
43
  These are the actual built component with **synthetic** account/workspace responses, not the production Desktop. [Capture provenance and artifact hashes](./images/dual-model-provenance.json) identify the exact evidence.
@@ -49,6 +57,7 @@ Regression evidence is separated deliberately:
49
57
  - `dual-model-card.spec.ts`: real React DOM/jsdom interaction, bilingual copy, CAS, unavailable models, stale responses and uncertain-create receipt handling.
50
58
  - `dual-model-ui.spec.ts`: optional Slot registration, fallback and cleanup.
51
59
  - `dual-model-remote.spec.ts` / `dual-model-gateway.spec.ts`: strict owned codecs and the actual installed Client Gateway with synthetic RPC. Older Client Gateways do not sanitize successful values or arbitrary nested error details; the Host builds bounded DTOs, and the UI renders only its diagnostic allowlist.
60
+ - `tests/scripts/dual-model-host-gateway.test.mjs`: postbuild Node tests drive the real public Host Connection Fetch handler and Gateway, bypassing Vitest's protocol stub. They cover endpoint exposure, unsupported capabilities, domain input validation, private-method refusal, disposal, and synthetic model/workspace view plus CAS save. SRC JSON fallback does not inherit the Client's strict descriptors; Host validation rejects malformed nested inputs.
52
61
  - `dual-model-host.spec.ts`: actual Core Session/projection/scope/tool primitives combined with synthetic Agent, model, persistence and workspace edges. This is not a paid live model run or a full installed Desktop certification.
53
62
  - `tests/browser/serve-dual-model.mjs`: serves the actual built Client component on a separate loopback fixture using synthetic account/workspace responses. Run after `pnpm build`; the printed URL is explicitly **not** the production DSH GUI.
54
63
 
@@ -1,8 +1,8 @@
1
- # Provider-aware web search routing (issues #112 and #118, alpha.17)
1
+ # Provider-aware web search routing
2
2
 
3
3
  ## Requested behavior
4
4
 
5
- The initiating Session chooses the native route, not the global default model. `github-copilot-search-routing.searchMode: auto` prefers eligible native search for canonical Copilot and verified plugin-owned `github-copilot-preview` selections, then uses `defaultSearchProvider` for every model without eligible native search. `fixed` always uses that provider. `none` disables only the default/fixed path; chat remains available. Provider-specific credentials and model selection remain provider-owned: `github-copilot.searchModel` supplies the explicit account-authorized Responses model when Copilot search runs independently of the chat model.
5
+ Issue #135 separates the primary search provider from the final fallback. `github-copilot-search-routing.searchProvider` is `auto` or an exact registered search-provider ID; `defaultSearchProvider` is an independent ID or `none`. Auto follows the initiating Chat provider, never the global default. Copilot's owned aliases retain their existing account/model checks; other Chat IDs must exactly match an observed search registration, without name/suffix/model-family inference. This is a provider-level convention, not a promise of the same execution model or account across independent registries. GPT, Gemini and Grok models do not each become a separate Copilot search provider. Explicit/fallback Copilot uses its provider-owned `github-copilot.searchModel`.
6
6
 
7
7
  All implementation belongs to this plugin. No Core source, deployed Core artifact, prototype, private registry, credential copy or global chat-model choice is modified. The original official WebRuntime configuration, including its selected search/fetch providers, is preserved. The routed facade mirrors public search-provider registrations so exact-id selection works for official and community providers without reading Core's private registry.
8
8
 
@@ -20,13 +20,21 @@ This is a plugin-owned subclass and explicit public service composition, not a r
20
20
 
21
21
  ## Routing and fallback
22
22
 
23
- `src/search-routing.ts` retains the native Copilot primary/fallback policy using captured provider/model leaves, a verified managed-route ownership flag and operation-local dependencies. The outer routed-web policy chooses whether to invoke that native path or an exact registered provider. It never substitutes the future global chat model. When `github-copilot-hosted` is selected independently, it uses only the explicit provider-owned `github-copilot.searchModel`; successful empty results do not trigger another fallback.
23
+ `src/search-tool-routing.ts` executes an operation-local primary and at most one distinct final fallback. A primary success, including zero sources, ends routing. Failure never retries the same provider as its own fallback, recurses or adds an original-Web hidden third tier. Caller/owner/policy cancellation, facade disposal, captured registration replacement and account-proof invalidation stop the operation rather than authorizing another paid attempt. Generic providers own their internals and must honor the supplied cancellation signal.
24
24
 
25
- `src/deepseek-search-fallback.ts` lazily constructs the public official `DeepSeekSearchProvider`; the upstream class owns its wire protocol, redirect policy and result parsing. The helper reads the existing `web-search-deepseek` settings, public launch-environment snapshot and credential service at operation time, and records the auxiliary request without auth headers/credential fields on the captured Session. Fallback API bases must be HTTP(S), without userinfo, query or fragment; invalid bases fail with a fixed diagnostic before auth, recording or fetch, preventing conventional URL-embedded credentials from entering the request log. Allowed bases/paths are not rewritten. The adapter id identifies the implementation; a custom configured endpoint need not be the default DeepSeek host. No credentials are retained as a new store.
25
+ The facade captures registered implementations, not merely IDs that could point to a replacement after an await. The main router resolves Auto from captured initiating-session provider leaves; a fixed selection is not overridden by Copilot inline search or prompt guidance. Copilot's special native/configured paths require its own registered implementation and existing ownership checks, never a third party that happens to register the same ID.
26
26
 
27
- Fallback notices are part of `WebSearchResult.content`, not merely an outer Tool result `content` or `meta`: Core may regenerate those outer fields from the canonical value. The notice names DeepSeek fallback, a bounded error reason and possible DeepSeek API charges, and explicitly says that the result is not from Copilot. Per-query notices remain available when the official consumer merges multiple queries. No search-time approval dialog is introduced.
27
+ Fallback notices remain inside canonical `WebSearchResult.content`, so Core result projection and query merging retain them. They identify the actual registered backend and possible charges, without copying arbitrary exception messages. Generic backends have no public endpoint/model provenance contract; those fields must not be guessed from the Chat model. Provider-owned safe content can include more precise provenance.
28
28
 
29
- `routeWebSearch` defaults to true; disabling it delegates through the original official service. Plugin-owned cross-provider settings live under `github-copilot-search-routing`: `searchMode` is `auto` or `fixed`, and `defaultSearchProvider` is an exact registered provider id or `none`. The Models page owns this routing card. `github-copilot.searchModel` remains provider-owned and is required when `github-copilot-hosted` serves independently. Existing `searchFallback` continues to govern failure after auto mode has selected native Copilot search only when `deepseek-official` is also the configured default; another selected default is never silently replaced by a paid DeepSeek request. The existing master enable switch, provider allowlist, lazy account metadata and native capability proof remain in effect.
29
+ Legacy direct callers without the facade capture seam keep the historical `src/search-routing.ts` Copilot/DeepSeek path only for legacy policy. Its `src/deepseek-search-fallback.ts` constructs the unchanged public official adapter and retains its safe URL validation, exact option snapshot, credential ownership and post-auth/pre-fetch guard. The new catalog-based path instead calls the actual registered backend; an identical ID does not authorize replacing it with that factory. Generic `search()` exposes no internal authentication/pre-network hook, so do not claim the stronger legacy guard for arbitrary third-party implementations.
30
+
31
+ `src/search-routing-policy.ts` normalizes settings without writes: a new explicit `searchProvider` wins; old auto remains Auto; old fixed preserves its default as primary; old fixed plus `none` stays disabled. Unknown saved IDs remain visible and fail closed at dispatch. Until an explicit UI save adopts the new fallback choice, legacy `searchFallback: none` preserves its failure-spending restriction. The save writes primary and default independently in the existing namespace with CAS. Copilot model configuration remains a separate namespace; a partial model save retains its new revision and reports the unconfirmed routing save. `routeWebSearch: false`, the master enable switch, Copilot allowlists and capability proof keep their existing responsibilities.
32
+
33
+ ## Search directory and settings UI
34
+
35
+ Both selectors use `githubCopilotSearchRouting.providers()`, a separate strict Remote exposed through the public `TypertRemoteService` binding. Its catalog contains only fresh IDs from registrations observed by this facade, not Core private registry objects or hidden/direct-original registrations. The read invokes no `available()`, auth, discovery or search: availability is contextual and checked on actual use. A missing facade yields `supported: false`, not a static example list. Newly registered IDs appear on reload; removed saved entries remain visibly unavailable. Primary adds Auto; default adds None. Models are provider-specific configuration, not additional search-provider entries.
36
+
37
+ Focused postbuild checks must run after `pnpm build`. `tests/search-provider-catalog.spec.ts` checks registration lifecycle, duplicate handling and read purity; `tests/scripts/search-provider-gateway.test.mjs` drives the real Connection Fetch/Gateway against the packaged facade and emitted Remote controller. `tests/web-search-routing-card.spec.ts` covers both selectors, unavailable catalogs, narrow CAS, legacy choices, stale generations and partial saves. These fixtures are not live account, installed Desktop or paid model evidence.
30
38
 
31
39
  ## Review hardening
32
40