Merge remote-tracking branch 'origin/master' into worktree/pr468-retarget-latest-master

# Conflicts:
#	docs/architecture.i18n.yaml
#	docs/capability-seams.md
#	docs/config-catalog.md
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	examples/acp-agent/tests/goal-snapshots/goal-session/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/bash-spill/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/both-mode-turn/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/cancel-tool-calls/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/cancel/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/code-mode-turn/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/code-mode-workspace-context/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/config-options/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/error-finish/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/escalation-approved/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/escalation-rejected/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/fs-edit/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/fs-escalation-approved/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/fs-policy-reject/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/fs-read-window/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/fs-read/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/fs-terminal-card/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/fs-write-overwrite/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/fs-write/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/goal-command-status/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/handshake/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-posttool-block/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-posttool-context/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-pretool-deny/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-promptsubmit-block/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-promptsubmit-context/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-stop-continue/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-posttool-block/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-posttool-context/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-pretool-block/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-promptsubmit-block/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-promptsubmit-context/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-stop-continue/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/lsp-definition/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/model-switching/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/multi-turn/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/parallel-tool-calls/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/permission-switching/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/repeat-tool-guard/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/skill-load/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/subagent-fork/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/subagent-mixed/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/subagent-multi/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/subagent-spawn/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/text-turn/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/todo-plan/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/tool-call-turn/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/workflow-run/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/workspace-context/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/workspace-edit/stdout.expected.jsonl
#	packages/ui/acp/acp-feature-support.md
#	packages/ui/acp/src/index.ts
#	packages/ui/acp/tests/harness.ts
#	packages/ui/tui/README.md
#	scripts/type-equiv.manifest.json
This commit is contained in:
Tianyi Cui
2026-07-22 21:06:50 +08:00
611 changed files with 42262 additions and 1200 deletions

View File

@@ -40,10 +40,12 @@ interface PackageManifest {
bin?: string | Record<string, string>
exports?: Record<
string,
| string
| {
types?: string
default?: string
}
| null
| undefined
>
files?: string[]
@@ -115,13 +117,41 @@ function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] {
'lib/invariant.js',
...manifest.bin ? ['lib/bin.js'] : [],
...manifest.exports?.['./worker'] ? ['lib/worker.cjs'] : [],
// UI plugin packages ship their browser bundle beside the node lib
// (single-artifact ruling: dist/ retired, ./client resolves lib/client.js).
// Keyed on the artifact path, not the subpath name: apiproxy's ./client is
// a browser-safe source channel, not a bundle.
...exportDefault(manifest, './client') === './lib/client.js' ? ['lib/client.js'] : [],
// runtime's shell-held loader subpath ships as its own bundle beside the client half.
...exportDefault(manifest, './loader') === './lib/loader.js' ? ['lib/loader.js'] : [],
// web-react's store subpath ships its own bundle (single-entry builds; no shared chunk).
...exportDefault(manifest, './store') === './lib/store/index.js' ? ['lib/store/index.js'] : [],
...extras,
// Subpaths whose runtime default is the tsc-emitted tree (lib/types/*.js —
// browser-safe source channels rehomed off src so plain Node can import
// them without type stripping) publish the emitted JS alongside the
// declarations.
...usesEmittedTreeDefaults(manifest) ? ['lib/types/**/*.js'] : [],
'lib/types/**/*.d.ts',
'lib/types/**/*.d.ts.map',
'src',
]
}
/** Runtime target of an export entry: conditional `default`, or the bare-string shorthand. */
function exportDefault(manifest: PackageManifest, subpath: string): string | undefined {
const entry = manifest.exports?.[subpath]
if (typeof entry === 'string') return entry
if (typeof entry === 'object' && entry !== null) return entry.default
return undefined
}
/** Whether any export's runtime default points into the tsc-emitted lib/types tree. */
function usesEmittedTreeDefaults(manifest: PackageManifest): boolean {
return Object.keys(manifest.exports ?? {}).some(subpath =>
exportDefault(manifest, subpath)?.startsWith('./lib/types/') === true)
}
function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
const errors: string[] = []
const label = manifest.name ?? dir
@@ -155,13 +185,16 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
if (manifest.types !== 'lib/types/index.d.ts') {
errors.push(`${label}: package.json must set "types": "lib/types/index.d.ts"`)
}
if (manifest.exports?.['.']?.types !== './lib/types/index.d.ts') {
const rootExport = manifest.exports?.['.']
const rootEntry = typeof rootExport === 'object' && rootExport !== null ? rootExport : undefined
if (rootEntry?.types !== './lib/types/index.d.ts') {
errors.push(`${label}: package.json exports["."].types must be "./lib/types/index.d.ts"`)
}
if (manifest.exports?.['.']?.default !== './lib/index.js') {
if (rootEntry?.default !== './lib/index.js') {
errors.push(`${label}: package.json exports["."].default must be "./lib/index.js"`)
}
const invariantExport = manifest.exports?.['./invariant']
const invariantRaw = manifest.exports?.['./invariant']
const invariantExport = typeof invariantRaw === 'object' && invariantRaw !== null ? invariantRaw : undefined
if (invariantExport?.types !== undefined && invariantExport.types !== './lib/types/invariant.d.ts') {
errors.push(`${label}: package.json exports["./invariant"].types must be "./lib/types/invariant.d.ts"`)
}

View File

@@ -0,0 +1,60 @@
/**
* Pins the client-bundle purity gate (tsdown preset resolveId classifier):
* a bare-name import of a module-table package must rewrite to its /client
* external form (inlining it duplicates runtime identity — the P0
/* leak that is not an
* inline-safe wire layer must fail the build loudly.
*/
import { describe, expect, it } from 'vitest'
import { CLIENT_EXTERNALS, clientBundle } from '../packages/client/tsdown.client.ts'
type ResolveId = (source: string) => null | { id: string; external: boolean }
function purityResolveId(): ResolveId {
// libEntry is spelled at every call site (no default) so the
// package-invariants text check can see the invariant entry per package.
const configs = clientBundle('@deepseek-ai/dsh-client-test', ['lib/types/index.js', 'lib/types/invariant.js'])
const plugins = (configs[1] as { plugins: { name: string; resolveId?: unknown }[] }).plugins
const gate = plugins.find(p => p.name === 'dsh-client-bundle-purity')
if (gate?.resolveId === undefined) throw new Error('purity plugin missing from client config')
return gate.resolveId as ResolveId
}
describe('client bundle purity gate', () => {
const resolveId = purityResolveId()
it('leaves table entries and non-scoped specifiers alone', () => {
expect(resolveId('@deepseek-ai/dsh-client-ui-slots')).toBeNull()
expect(resolveId('@deepseek-ai/dsh-client-runtime/client')).toBeNull()
expect(resolveId('react')).toBeNull()
expect(resolveId('zod')).toBeNull()
})
it('rewrites a bare table-package name to its external /client form (duplicate-instance prevention)', () => {
expect(resolveId('@deepseek-ai/dsh-client-connection')).toEqual({
id: '@deepseek-ai/dsh-client-connection/client',
external: true,
})
expect(resolveId('@deepseek-ai/dsh-client-ui-layout')).toEqual({
id: '@deepseek-ai/dsh-client-ui-layout/client',
external: true,
})
})
it('lets inline-safe wire layers inline', () => {
expect(resolveId('@deepseek-ai/dsh-host-apiproxy/api')).toBeNull()
expect(resolveId('@deepseek-ai/dsh-session/surface')).toBeNull()
expect(resolveId('@deepseek-ai/dsh-brand')).toBeNull()
})
it('throws on any other @deepseek-ai leak', () => {
expect(() => resolveId('@deepseek-ai/dsh-agent')).toThrow(/purity/)
expect(() => resolveId('@deepseek-ai/dsh-client-web')).toThrow(/purity/)
})
it('every /client external has no bare-name twin in the table (the rewrite assumption)', () => {
for (const entry of CLIENT_EXTERNALS) {
if (entry.endsWith('/client')) expect(CLIENT_EXTERNALS).not.toContain(entry.slice(0, -'/client'.length))
}
})
})

View File

@@ -1,11 +1,22 @@
/** Map one workspace source alias target to its declaration-build target. */
export function builtDeclarationPath(candidate: string): string {
// Two workspace shapes exist: whole-package entries end in /src, subpath
// wildcards (apiproxy's browser-safe /api and /client channels) in /src/*.
if (candidate.endsWith('/src')) {
return `${candidate.slice(0, -'/src'.length)}/lib/types`
}
if (candidate.endsWith('/src/*')) {
return `${candidate.slice(0, -'/src/*'.length)}/lib/types/*`
}
const sourceFile = /^(.*)\/src\/(.+)\.ts$/.exec(candidate)
if (sourceFile?.[1] && sourceFile[2]) {
return `${sourceFile[1]}/lib/types/${sourceFile[2]}.d.ts`
}
// Directory subpath entries (web-react's /store, runtime's /client): the
// source dir maps to the same dir under lib/types (index resolution applies).
const sourceDir = /^(.*)\/src\/(.+)$/.exec(candidate)
if (sourceDir?.[1] && sourceDir[2]) {
return `${sourceDir[1]}/lib/types/${sourceDir[2]}`
}
throw new Error(`doc-typecheck: cannot map workspace source path to built declarations: ${candidate}`)
}

View File

@@ -182,6 +182,9 @@ const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
CompactAgentContext: 'compaction service input is owned by packages/compact/compact/src/index.ts',
CreateAgentOptions: 'agent creation contract is owned by packages/core/agent/README.md',
InvariantInstaller: 'service-local contribution contract is owned by packages/support/invariants/README.md',
LocaleDict: 'service-local dictionary shape is owned by packages/client/i18n/src/index.ts',
ThemeTokens: 'service-local token dictionary is owned by packages/client/ui-theme/src/index.ts',
Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts',
InvariantRegistration: 'service-local lifecycle handle is owned by packages/support/invariants/README.md',
PresetOption: 'deployment menu metadata is owned by packages/ui/permission/README.md',
PresetSpec: 'deployment preset composition is owned by packages/ui/permission/README.md',

View File

@@ -70,6 +70,7 @@ const GROUP_ORDER = [
'web',
'spill',
'todo',
'plan',
'cordis',
'hooks',
'session-persistence',
@@ -179,6 +180,14 @@ const SERVICE_ROLES: ServiceRole[] = [
consumers: ['tool-ask-user', 'tui', 'acp'],
note: 'UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise.',
},
{
key: 'planMode',
pkg: 'plan-mode',
title: 'Plan collaboration state',
mode: 'core',
consumers: ['acp'],
note: 'Folds logged plan/mode state, flushes user selections at turn boundaries, renders deployment-owned guidance, registers /plan, and keeps the plan-exit schema stable across transitions.',
},
{
key: 'commands',
pkg: 'commands',

View File

@@ -31,6 +31,7 @@ const GROUP_ORDER = [
'spill',
'timeout',
'todo',
'plan',
'cordis',
'hooks',
'session-persistence',

View File

@@ -19,6 +19,7 @@ import type { BashExecRequest, BashExecSpec, BashProcess, BashRunResult } from '
import LocalBashExecutor from '@deepseek-ai/dsh-bash-local'
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
import PlanModeService from '@deepseek-ai/dsh-plan-mode'
import WebService from '@deepseek-ai/dsh-web'
import * as WebSearchExa from '@deepseek-ai/dsh-web-search-exa'
import * as WebFetchLocal from '@deepseek-ai/dsh-web-fetch-local'
@@ -172,6 +173,18 @@ const TOOL_PACKAGES: ToolPackage[] = [
note:
'Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: code` / `mode: both` (see the Code Mode Agent Note). Under `code` it is the registry\'s only wire contribution; the other visible capabilities are declared in a generated TypeScript SDK section, and a program calls them through serialized bindings that re-enter the complete guarded tool pipeline and link each nested execution to this outer result.',
},
{
pkg: '@deepseek-ai/dsh-plan-mode',
dir: 'plan-mode',
source: 'packages/plan/plan-mode/src/index.ts',
requires: ['ctx.tools', 'ctx.systemPrompt', 'ctx.userInteraction (execution time, opportunistic)'],
writes: ['tool/call', 'plan/mode inactive on an approved review', 'tool/result'],
async mount(ctx) {
await ctx.plugin(PlanModeService, { section: 'Tool catalog schema harvest.' })
},
note:
'exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-interaction seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary.',
},
{
pkg: '@deepseek-ai/dsh-tool-bash',
dir: 'tool-bash',

View File

@@ -18,6 +18,7 @@ type Mode =
| 'ci-artifacts'
| 'node-compat'
| 'pre-push'
| 'manual-push'
| 'doc-sync'
type GateStatus = 'pending' | 'running' | 'passed' | 'failed' | 'skipped'
@@ -191,14 +192,17 @@ function gatesForMode(selected: Mode): Gate[] {
'packages/session-persistence/session-persistence-jsonl/tests/zstd.compat.spec.ts',
], { label: 'JSONL Zstandard smoke' }),
]
case 'pre-push':
case 'pre-push': return []
case 'manual-push':
return [
pnpmScript('runtime-closure', 'verify-runtime-closure', { label: 'runtime closure' }),
pnpmScript('cordis-config', 'verify-cordis-config', { label: 'Cordis config' }),
pnpmScript('client-domain-graph', 'verify-client-domain-graph', { label: 'client domain graph' }),
pnpmScript('test', 'test'),
pnpmScript('duplication', 'duplication'),
snapshotGate(),
pnpmScript('build', 'build'),
pnpmScript('build:web', 'build:web'),
...hygieneLeafGates({ artifactNeeds: ['build'] }),
...docSyncLeafGates({
docTypecheckNeeds: ['build'],

View File

@@ -1,6 +1,13 @@
{
"requiredSince": "2026-07-14",
"required": [
".agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md",
".agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md",
".agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md",
".agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md",
".agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md",
".agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md",
".agents/notes/implemented/process/2026-07-19-web-styling-system.md",
"README.md",
"docs/cookbook/adding-a-package.md",
"docs/cookbook/adding-a-tool.md",
@@ -23,9 +30,6 @@
"docs/user/guide/index.md",
"docs/user/guide/quickstart.md",
"docs/user/index.md",
".agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md",
".agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md",
".agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md",
"python/README.md",
"python/sdk-runtime/README.md",
"python/sdk/README.md"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
/**
* Enforce intra-package domain layering inside `packages/client/*\/src/client/`.
* verify-module-graph covers package-level edges; this gate covers the
* directory level the future package split will land on: domain directories
* may import `contract/` and never each other, and only the assembly point
* (`apply.ts` / `index.ts`) may import across domains.
*
* Layer model (lower may not import higher):
* 0 contract/ shared contract surface (types + slot declarations)
* 1 <domain>/ + service domain implementations (skeleton/, chat/, ...)
* 2 apply.ts, index.ts assembly point and re-export shell
*
* Not yet wired into the gate sequence (loose-gate window); run directly:
* pnpm exec tsx scripts/verify-client-domain-graph.ts
*/
import { readdirSync, readFileSync, statSync } from 'node:fs'
import { join, resolve } from 'node:path'
const root = resolve(import.meta.dirname, '..')
const CLIENT_DIR = join(root, 'packages/client')
/** Directory names treated as the shared contract layer (importable by all). */
const CONTRACT_DIRS = new Set(['contract'])
/** Top-level client files allowed to import across domains (assembly layer). */
const ASSEMBLY_FILES = new Set(['apply.ts', 'index.ts', 'index.tsx'])
interface Violation { file: string; imported: string; reason: string }
/** Recursively list .ts/.tsx files under dir (relative paths). */
function listSources(dir: string, prefix = ''): string[] {
const out: string[] = []
for (const name of readdirSync(dir)) {
const full = join(dir, name)
const rel = prefix ? `${prefix}/${name}` : name
if (statSync(full).isDirectory()) out.push(...listSources(full, rel))
else if (/\.tsx?$/.test(name) && !/\.legacy\./.test(name)) out.push(rel)
}
return out
}
/** First path segment of a client-relative file, or '' for top-level files. */
function domainOf(rel: string): string {
const ix = rel.indexOf('/')
return ix === -1 ? '' : rel.slice(0, ix)
}
function checkPackage(pkgName: string, clientDir: string): Violation[] {
const violations: Violation[] = []
const files = listSources(clientDir)
for (const rel of files) {
const fromDomain = domainOf(rel)
const isAssembly = fromDomain === '' && ASSEMBLY_FILES.has(rel)
if (isAssembly) continue
const source = readFileSync(join(clientDir, rel), 'utf8')
for (const match of source.matchAll(/from\s+['"](\.[^'"]+)['"]/g)) {
const spec = match[1]
if (spec === undefined) continue
// Resolve the relative specifier against the importing file's directory
// to a client-dir-relative path.
const fromDir = rel.includes('/') ? rel.slice(0, rel.lastIndexOf('/')) : ''
const parts = (fromDir ? fromDir.split('/') : [])
for (const seg of spec.split('/')) {
if (seg === '.') continue
if (seg === '..') parts.pop()
else parts.push(seg)
}
const target = parts.join('/')
if (target.startsWith('..')) continue // out of client dir (package root) — package-level rules govern
const toDomain = domainOf(target)
if (toDomain === '' || CONTRACT_DIRS.has(toDomain)) continue // top-level shared file or contract layer
if (fromDomain === toDomain) continue // inside one domain
violations.push({
file: `${pkgName}/src/client/${rel}`,
imported: spec,
reason: fromDomain === ''
? `top-level non-assembly file imports domain "${toDomain}" (only apply/index may assemble)`
: `domain "${fromDomain}" imports sibling domain "${toDomain}" (route shared surface through contract/)`,
})
}
}
return violations
}
const violations: Violation[] = []
for (const pkg of readdirSync(CLIENT_DIR)) {
const clientDir = join(CLIENT_DIR, pkg, 'src/client')
try {
if (!statSync(clientDir).isDirectory()) continue
} catch {
// No client half in this package — nothing to layer-check.
continue
}
violations.push(...checkPackage(pkg, clientDir))
}
if (violations.length > 0) {
console.error(`verify-client-domain-graph: ${violations.length} violation(s):`)
for (const v of violations) console.error(` ${v.file} -> ${v.imported}\n ${v.reason}`)
process.exit(1)
}
console.log('verify-client-domain-graph: client domain layering clean.')

View File

@@ -152,15 +152,29 @@ function localPackageDirectories(): Map<string, string> {
}
function rootProjectReferences(): Set<string> {
const config = ts.readConfigFile(resolve(root, 'tsconfig.json'), path => ts.sys.readFile(path))
if (config.error !== undefined) {
throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'))
// Typecheck runs two sibling aggregates (root = host program,
// tsconfig.client.json = client program; the two sides merge cordis Context
// under the same keys, so one program cannot see both). Seed both and follow
// any nested aggregate references to collect the covered leaf project set.
const collected = new Set<string>()
const queue = [resolve(root, 'tsconfig.json'), resolve(root, 'tsconfig.client.json')]
const seen = new Set<string>()
for (let file = queue.pop(); file !== undefined; file = queue.pop()) {
if (seen.has(file)) continue
seen.add(file)
const config = ts.readConfigFile(file, path => ts.sys.readFile(path))
if (config.error !== undefined) {
throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'))
}
const references = (config.config as { references?: Array<{ path?: unknown }> }).references ?? []
for (const reference of references) {
if (typeof reference.path !== 'string') continue
const target = resolve(dirname(file), reference.path)
if (target.endsWith('.json')) queue.push(target)
else collected.add(target)
}
}
const references = (config.config as { references?: Array<{ path?: unknown }> }).references ?? []
return new Set(references.flatMap((reference) => {
if (typeof reference.path !== 'string') return []
return [resolve(root, reference.path)]
}))
return collected
}
function packageNameFromSpecifier(specifier: string): string | undefined {

View File

@@ -79,7 +79,10 @@ Object.defineProperty(globalThis, 'window', { value: window })
Object.defineProperty(globalThis, 'document', { value: window.document })
Object.defineProperty(globalThis, 'navigator', { value: window.navigator })
const mermaid = (await import('mermaid')).default
mermaid.initialize({ startOnLoad: false })
// maxEdges: mermaid's default 500-edge render guard; the module graph grows
// with every package edge and crossed it legitimately. Raise the guard here
// (a secure config settable only via initialize) rather than trimming edges.
mermaid.initialize({ startOnLoad: false, maxEdges: 1000 })
for (const block of blocks) {
try {
await mermaid.parse(block.source, { suppressErrors: false })

View File

@@ -45,11 +45,26 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/bash/bash-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-bash.' },
'packages/code-runtime/code-runtime': { kind: 'indirect', reason: 'The service interface delegates model rendering to Code Mode in dsh-tools.' },
'packages/code-runtime/code-runtime-worker': { kind: 'indirect', reason: 'The worker backend delegates model rendering to Code Mode in dsh-tools.' },
'packages/client/ui-slots': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-primitives': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/web-react': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/connection': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/runtime': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-layout': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-sidebar': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-conversation': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-trajectory': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-theme': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/i18n': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/web': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/examples/agent-spine-demo': { kind: 'indirect', reason: 'The bundle only mounts model-facing child plugins.' },
'packages/fs/fs': { kind: 'indirect', reason: 'The service interface delegates model rendering to dsh-tool-fs.' },
'packages/fs/fs-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' },
'packages/fs/fs-sandbox': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' },
'packages/hooks/hook-protocol': { kind: 'indirect', reason: 'Only the hook bridge plugins render decoded hook output to a model.' },
'packages/host/apiproxy': { kind: 'none', reason: 'The wire contract and fetch carriers move already-composed messages and register no model surface.' },
'packages/host/runtime': { kind: 'indirect', reason: 'The assembly mounts model-facing plugins and injects provider/model defaults into agents.' },
'packages/host/webserver': { kind: 'none', reason: 'The HTTP carrier bridges browser and API handler and registers no model surface.' },
'packages/llm/llm': { kind: 'none', reason: 'The adapter registry forwards already-assembled requests unchanged.' },
'packages/llm/token-meter': { kind: 'indirect', reason: 'The measurement service leaves model-visible changes to its consumers.' },
'packages/lsp/lsp': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-lsp.' },