Merge origin/master into worktree/skill-catalog-hot-refresh

This commit is contained in:
Tianyi Cui
2026-07-29 16:40:40 +08:00
1089 changed files with 35627 additions and 9637 deletions

View File

@@ -0,0 +1,33 @@
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import * as yaml from 'js-yaml'
import { describe, expect, it } from 'vitest'
const root = resolve(import.meta.dirname, '..')
const runnerPrivatePnpmDestination = '${{ runner.temp }}/setup-pnpm'
describe('CI workflow', () => {
it('isolates every pnpm action setup destination per runner', () => {
const workflow: unknown = yaml.load(readFileSync(resolve(root, '.github/workflows/ci.yml'), 'utf8'))
if (!isRecord(workflow) || !isRecord(workflow.jobs)) throw new TypeError('CI workflow must define jobs')
const setups = Object.entries(workflow.jobs).flatMap(([jobName, job]) => {
if (!isRecord(job) || !Array.isArray(job.steps)) return []
return job.steps.flatMap((step) => {
if (!isRecord(step) || typeof step.uses !== 'string' || !step.uses.startsWith('pnpm/action-setup@')) return []
return [{ jobName, step }]
})
})
expect(setups.length).toBeGreaterThan(0)
for (const { jobName, step } of setups) {
expect(step, `${jobName} must not share pnpm/action-setup's default destination`).toMatchObject({
with: { dest: runnerPrivatePnpmDestination },
})
}
})
})
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}

View File

@@ -7,7 +7,13 @@ import { spawn } from 'node:child_process'
// Each UI's node invocation matches its base demo script plus the overlay config.
const UIS = new Map([
['tui', ['--import', 'tsx', 'apps/cli/src/bin.ts', '--config', 'examples/tui-agent/code-mode.cordis.yml']],
['tui', [
'--import',
'tsx/esm',
'apps/cli/src/bin.ts',
'--config',
'examples/tui-agent/code-mode.cordis.yml',
]],
['acp', ['--import', 'tsx', 'packages/examples/acp-demo/src/bin.ts', '--config', 'examples/acp-agent/code-mode.cordis.yml']],
])

View File

@@ -1,5 +1,5 @@
{
"AGENTS.md": 1705,
"AGENTS.md": 1750,
"docs/AGENTS.md": 1150,
"docs/architecture.md": 1800,
"docs/cordis-primer.md": 600,
@@ -7,5 +7,5 @@
"docs/testing.md": 1100,
"examples/AGENTS.md": 310,
"packages/AGENTS.md": 675,
"packages/README.md": 835
"packages/README.md": 870
}

View File

@@ -36,8 +36,7 @@ export const LINK_MAP: Record<string, string> = {
ContinuationStop: 'core.md',
GenerateOptions: 'core.md',
InboxPlacement: 'core.md',
AgentMessage: 'core.md',
AgentMessageId: 'core.md',
MessageId: 'core.md',
HookContext: 'core.md',
SettleReason: 'core.md',
LlmCallConfig: 'core.md',
@@ -50,6 +49,7 @@ export const LINK_MAP: Record<string, string> = {
ResolvedRetryPolicy: 'llm-streaming.md',
Message: 'core.md',
MessageSource: 'core.md',
UserMessage: 'session.md',
PromptDecision: 'core.md',
RequestErrorAction: 'core.md',
RequestError: 'core.md',
@@ -211,6 +211,7 @@ const FOUNDATION_TYPE_NAMES = new Set([
'AsyncIterable',
'Context',
'Error',
'Partial',
'Pick',
'Promise',
'Readonly',
@@ -227,6 +228,7 @@ const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
BashEnvContributor: 'service-local extension type is owned by packages/bash/tool-bash/src/index.ts',
BashEnvVariableInfo: 'service-local metadata type is owned by packages/bash/tool-bash/src/index.ts',
CompactAgentContext: 'compaction service input is owned by packages/compact/compact/src/index.ts',
DirectoryPickerCapability: 'picker interaction contract is owned by packages/host/directory-picker/README.md',
CreateAgentOptions: 'agent creation contract is owned by packages/core/agent/README.md',
Domain: 'domain interface is owned by packages/storage/storage-domain/README.md',
DomainChanged: 'event-local snapshot is owned by packages/storage/storage-domain/src/events.ts',
@@ -235,6 +237,12 @@ const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
DomainSpec: 'domain declaration contract is owned by packages/storage/storage-domain/README.md',
StorageBackend: 'backend contract is owned by packages/storage/storage/src/backend.ts',
StorageForms: 'merge-extensible form map is owned by packages/storage/storage/src/index.ts',
ProjectionDefinition: 'projection unit contract is owned by packages/session-projection/session-projection/README.md',
SessionProjectionMap: 'merge-extensible projection key map is owned by packages/session-projection/session-projection/src/types.ts',
ProjectionChangeListener: 'change-feed listener contract is owned by packages/session-projection/session-projection/src/index.ts',
ProjectionSnapshot: 'watermark snapshot shape is owned by packages/session-projection/session-projection/src/index.ts',
ProjectionCheckpoint: 'persisted checkpoint row map is owned by packages/session-projection/session-projection/src/index.ts',
CommandExecution: 'executor return contract is owned by packages/ui/commands/src/index.ts',
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',
WebBootGraph: 'web boot graph wire shape is owned by packages/client/modules/src/client/index.ts',
@@ -246,6 +254,8 @@ const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
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',
KnobState: 'projection unit state shape is owned by packages/ui/permission/README.md',
PermissionSelect: 'permissions projection payload is owned by packages/ui/permission/src/types.ts',
PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md',
ResumeAgentOptions: 'agent resume contract is owned by packages/core/agent/README.md',
SessionForkSource: 'service-local fork input is owned by packages/core/session/src/index.ts',

View File

@@ -236,6 +236,22 @@ const SERVICE_ROLES: ServiceRole[] = [
consumers: ['tui'],
note: 'Plugins register direct human commands; TUI consumes the effective per-agent catalog without sending invocations to the model.',
},
{
key: 'sessionProjections',
pkg: 'session-projection',
title: 'Session projection units',
mode: 'core',
consumers: ['tool-todo', 'session-title', 'host-apiproxy'],
note: 'Domains register state-driven fold units; the eager drive keeps per-session watermark states and api-proxy serves baselines and pushes changed values.',
},
{
key: 'sessionProjectionCache',
pkg: 'session-projection-cache',
title: 'Persisted projection cache',
mode: 'core',
consumers: ['host-apiproxy'],
note: 'Durably checkpoints projection unit states per session (throttled + turn/end/detach mandatory points) and serves the cold-read ladder: cache row + persistence tail replay, so listings never load full logs.',
},
{
key: 'tui',
pkg: 'tui',
@@ -408,6 +424,15 @@ const SERVICE_ROLES: ServiceRole[] = [
consumers: ['spill-policy'],
note: 'The backend saves oversized tool text and returns a model-facing locator plus retrieval hint; spill-policy is the tools/post-execute consumer that decides when to spill.',
},
{
key: 'directoryPicker',
pkg: 'directory-picker',
title: 'Workspace-directory picking seam',
mode: 'seam',
implementations: ['directory-picker-native', 'directory-picker-browse'],
consumers: ['apiproxy'],
note: 'Discriminated interaction capability: the native backend opens one OS chooser on the host display, the browse backend serves listing/creation primitives for the in-app browser; dual-face backends fill ui-workspace directory-flow slots from their browser halves (no wire advertisement).',
},
{
key: 'httpServer',
pkg: 'webserver',

View File

@@ -2,7 +2,7 @@
import { randomUUID } from 'node:crypto'
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
import { spawnSync } from 'node:child_process'
import { isAbsolute, join, resolve } from 'node:path'
import { dirname, isAbsolute, join, resolve } from 'node:path'
const MINIMUM_GIT = [2, 26, 0]
const HOOKS_DIRECTORY = 'dsh-hooks'
@@ -437,6 +437,15 @@ function inspectOwnedHooksDirectory(hooksPath) {
return { markerPath, ...marker }
}
function isRegisteredOwnedHooksPath(commonDirectory, hooksPath) {
const normalizedHooksPath = normalizedPath(hooksPath)
const isRegistered = registeredWorktreeConfigPaths(commonDirectory).some(
configPath => normalizedPath(join(dirname(configPath), HOOKS_DIRECTORY)) === normalizedHooksPath,
)
if (!isRegistered) return false
return inspectOwnedHooksDirectory(hooksPath)?.hooksPath === hooksPath
}
function ensureOwnedHooksDirectory(hooksPath) {
const inspected = inspectOwnedHooksDirectory(hooksPath)
if (inspected !== undefined) return inspected
@@ -561,14 +570,22 @@ async function main() {
'worktree core.hooksPath',
)
let ownedHooksDirectory
let copiedWorktreePathIsOwned = false
if (worktreePath !== undefined && worktreePath !== hooksPath) {
ownedHooksDirectory = inspectOwnedHooksDirectory(hooksPath)
if (ownedHooksDirectory === undefined || ownedHooksDirectory.hooksPath !== worktreePath) {
const worktreePathIsRelocated = ownedHooksDirectory?.hooksPath === worktreePath
copiedWorktreePathIsOwned = !worktreePathIsRelocated
&& isRegisteredOwnedHooksPath(commonDirectory, worktreePath)
if (!worktreePathIsRelocated && !copiedWorktreePathIsOwned) {
refuseScopedHooksPath({ origin: `file:${worktreeConfigPath}`, scope: 'worktree', value: worktreePath })
}
}
const directWorktreePathIsOwned = worktreePath !== undefined
&& (worktreePath === hooksPath || ownedHooksDirectory?.hooksPath === worktreePath)
&& (
worktreePath === hooksPath
|| ownedHooksDirectory?.hooksPath === worktreePath
|| copiedWorktreePathIsOwned
)
const effectiveEntry = effectiveConfigEntry(root, 'core.hooksPath')
if (effectiveEntry !== undefined) {
const effectivePathIsOwned = effectiveEntry.scope === 'worktree'
@@ -593,6 +610,7 @@ async function main() {
worktreePath !== undefined
&& worktreePath !== hooksPath
&& ownedHooksDirectory.hooksPath !== worktreePath
&& !copiedWorktreePathIsOwned
) {
throw new Error(`hooks directory ownership changed while relocating ${JSON.stringify(worktreePath)}`)
}

View File

@@ -262,6 +262,30 @@ describe('worktree-local Lefthook installer', () => {
expect(readFileSync(legacyHook, 'utf8')).toBe('#!/bin/sh\n# legacy hook\n')
})
it('replaces the owned hook path Git copies into a newly added worktree', async () => {
const fixture = createFixture()
const mainInstall = await runInstaller(fixture, fixture.main)
expect(mainInstall.status, mainInstall.stderr).toBe(0)
const mainHooks = hooksPath(fixture, fixture.main)
const mainHookBefore = readFileSync(join(mainHooks, 'pre-commit'), 'utf8')
const lateLinked = join(fixture.container, 'late-linked')
git(fixture, fixture.main, ['worktree', 'add', '-b', 'late-linked', lateLinked])
write(join(lateLinked, 'lefthook.yml'), 'late-linked-worktree-config\n')
installFakeLefthook(lateLinked)
expect(git(fixture, lateLinked, ['config', '--worktree', '--get', 'core.hooksPath'])).toBe(mainHooks)
const linkedInstall = await runInstaller(fixture, lateLinked)
expect(linkedInstall.status, linkedInstall.stderr).toBe(0)
const linkedHooks = hooksPath(fixture, lateLinked)
expect(linkedHooks).not.toBe(mainHooks)
expect(git(fixture, lateLinked, ['config', '--worktree', '--get', 'core.hooksPath'])).toBe(linkedHooks)
expect(readFileSync(join(linkedHooks, 'pre-commit'), 'utf8')).toContain(
'# config=late-linked-worktree-config',
)
expect(readFileSync(join(mainHooks, 'pre-commit'), 'utf8')).toBe(mainHookBefore)
})
it('serializes concurrent installs and keeps repeated output stable', async () => {
const fixture = createFixture()
const delayed = { DSH_TEST_LEFTHOOK_DELAY_MS: '150' }
@@ -509,6 +533,30 @@ describe('worktree-local Lefthook installer', () => {
expect(git(fixture, fixture.linked, ['config', '--worktree', '--get', 'core.hooksPath'])).toBe('linked-custom-hooks')
})
it('does not trust an ownership marker outside a registered worktree hook path', async () => {
const fixture = createFixture()
const mainInstall = await runInstaller(fixture, fixture.main)
expect(mainInstall.status, mainInstall.stderr).toBe(0)
const externalHooks = join(fixture.container, 'external-owned-hooks')
write(
join(externalHooks, '.dsh-lefthook-owned'),
`${JSON.stringify({
version: 1,
owner: 'deepseek-harness worktree-local lefthook hooks',
hooksPath: externalHooks,
})}\n`,
0o600,
)
git(fixture, fixture.linked, ['config', '--worktree', 'core.hooksPath', externalHooks])
const result = await runInstaller(fixture, fixture.linked)
expect(result.status).toBe(1)
expect(result.stderr).toContain('worktree-scoped core.hooksPath')
expect(git(fixture, fixture.linked, ['config', '--worktree', '--get', 'core.hooksPath'])).toBe(externalHooks)
expect(existsSync(hooksPath(fixture, fixture.linked))).toBe(false)
})
it('refuses to activate a sibling worktree dormant hook path', async () => {
const fixture = createFixture()
const linkedConfig = join(gitDirectory(fixture, fixture.linked), 'config.worktree')

View File

@@ -287,6 +287,11 @@ function nodeCompatSmokeGates(): Gate[] {
'run',
'packages/session-persistence/session-persistence-jsonl/tests/zstd.compat.spec.ts',
], { label: 'JSONL Zstandard smoke' }),
pnpmExec('dsh-source-launch-smoke', [
'vitest',
'run',
'apps/cli/tests/source-launch.compat.spec.ts',
], { label: 'dsh source-launch smoke' }),
]
}

File diff suppressed because one or more lines are too long

View File

@@ -14,17 +14,17 @@
{
"doc": "docs/core-data-structures/core.md",
"symbol": "AssistantProvenance",
"source": "packages/llm/llm/src/types.ts"
"source": "packages/llm/llm/src/message.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "Message",
"source": "packages/llm/llm/src/types.ts"
"source": "packages/llm/llm/src/message.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "MessageSourceMap",
"source": "packages/llm/llm/src/types.ts"
"source": "packages/llm/llm/src/message.ts"
},
{
"doc": "docs/core-data-structures/core.md",
@@ -101,16 +101,6 @@
"symbol": "SendOptions",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "AgentMessageId",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "AgentMessage",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "CancelOptions",
@@ -189,37 +179,37 @@
{
"doc": "docs/core-data-structures/goal.md",
"symbol": "GoalView",
"source": "packages/goal/goal/src/types.ts"
"source": "packages/goal/goal/src/domain.ts"
},
{
"doc": "docs/core-data-structures/goal.md",
"symbol": "GoalSnapshotChangeMeta",
"source": "packages/goal/goal/src/types.ts"
"source": "packages/goal/goal/src/domain.ts"
},
{
"doc": "docs/core-data-structures/goal.md",
"symbol": "GoalClearChangeMeta",
"source": "packages/goal/goal/src/types.ts"
"source": "packages/goal/goal/src/domain.ts"
},
{
"doc": "docs/core-data-structures/goal.md",
"symbol": "GoalMessageSource",
"source": "packages/goal/goal/src/types.ts"
"source": "packages/goal/goal/src/domain.ts"
},
{
"doc": "docs/core-data-structures/goal.md",
"symbol": "CreateGoalRequest",
"source": "packages/goal/goal/src/types.ts"
"source": "packages/goal/goal/src/domain.ts"
},
{
"doc": "docs/core-data-structures/goal.md",
"symbol": "EditGoalRequest",
"source": "packages/goal/goal/src/types.ts"
"source": "packages/goal/goal/src/domain.ts"
},
{
"doc": "docs/core-data-structures/goal.md",
"symbol": "GoalChanged",
"source": "packages/goal/goal/src/types.ts"
"source": "packages/goal/goal/src/domain.ts"
},
{
"doc": "docs/core-data-structures/commands.md",
@@ -320,8 +310,8 @@
},
{
"doc": "docs/core-data-structures/session.md",
"symbol": "UserMessageData",
"source": "packages/core/session/src/types.ts"
"symbol": "UserMessage",
"source": "packages/llm/llm/src/message.ts"
},
{
"doc": "docs/core-data-structures/session.md",

View File

@@ -1,10 +1,10 @@
/**
* Validate Cordis Loader entry metadata and example package resolution.
* Validate Cordis Loader entry metadata and package resolution.
*
* The Loader interpolates only a plugin entry's `config`; expression objects in
* fields such as `disabled` remain truthy data and silently change composition.
* Example configs run from built packages, so every named package must resolve
* from the examples workspace and every local package must be in the root
* Example configs and the dsh Web composition resolve named plugins from their
* owning workspace manifests. Local example packages must also be in the root
* TypeScript project graph.
*/
@@ -42,7 +42,7 @@ const schema = yaml.JSON_SCHEMA.extend(jsExprType)
const files = cordisConfigFiles(root)
const errors: string[] = []
const examplePluginReferences: PluginReference[] = []
const pluginReferences: PluginReference[] = []
for (const file of files) {
const document: unknown = yaml.load(readFileSync(resolve(root, file), 'utf8'), { schema })
@@ -56,9 +56,10 @@ for (const file of files) {
}
errors.push(...validateExampleResolution())
errors.push(...validateAppResolution())
if (errors.length > 0) {
console.error('verify-cordis-config: invalid Loader metadata or example package resolution:')
console.error('verify-cordis-config: invalid Loader metadata or plugin package resolution:')
for (const error of errors) console.error(`- ${error}`)
process.exitCode = 1
} else {
@@ -70,7 +71,7 @@ function validateEntry(value: unknown, file: string, path: string): void {
errors.push(`${file}${path}: entry must be an object`)
return
}
recordExamplePlugin(value, file)
recordPlugin(value, file)
validateMetadata(value, file, path)
if ((value.group === true || value.name === '@cordisjs/plugin-group') && isUnknownArray(value.config)) {
for (let index = 0; index < value.config.length; index++) {
@@ -84,7 +85,7 @@ function validateEntry(value: unknown, file: string, path: string): void {
const patch = config.patches[index]
const patchPath = `${path}.config.patches[${index}]`
if (!isRecord(patch)) continue
recordExamplePlugin(patch, file)
recordPlugin(patch, file)
validateMetadata(patch, file, patchPath)
if (!isUnknownArray(patch.insert)) continue
for (let insertIndex = 0; insertIndex < patch.insert.length; insertIndex++) {
@@ -93,10 +94,8 @@ function validateEntry(value: unknown, file: string, path: string): void {
}
}
function recordExamplePlugin(entry: Record<string, unknown>, file: string): void {
if (file.startsWith('examples/') && typeof entry.name === 'string') {
examplePluginReferences.push({ file, name: entry.name })
}
function recordPlugin(entry: Record<string, unknown>, file: string): void {
if (typeof entry.name === 'string') pluginReferences.push({ file, name: entry.name })
}
function validateExampleResolution(): string[] {
@@ -105,25 +104,13 @@ function validateExampleResolution(): string[] {
const dependencies = exampleManifest.dependencies ?? {}
const localPackages = localPackageDirectories()
const rootReferences = rootProjectReferences()
const requiredPackages = new Map<string, Set<string>>()
for (const reference of examplePluginReferences) {
const packageName = packageNameFromSpecifier(reference.name)
if (packageName === undefined) continue
const locations = requiredPackages.get(packageName) ?? new Set<string>()
locations.add(reference.file)
requiredPackages.set(packageName, locations)
}
for (const [packageName, locations] of requiredPackages) {
if (!(packageName in dependencies)) {
violations.push(`${[...locations].join(', ')}: ${packageName} must be declared in examples/package.json dependencies`)
}
}
const exampleReferences = pluginReferences.filter(reference => reference.file.startsWith('examples/'))
violations.push(...missingPluginDependencies(exampleReferences, dependencies, 'examples/package.json'))
const requiredPackages = new Set(exampleReferences.map(reference => packageNameFromSpecifier(reference.name)))
const localExamplePackages = new Set([
...Object.keys(dependencies),
...requiredPackages.keys(),
...[...requiredPackages].filter(packageName => packageName !== undefined),
])
for (const packageName of localExamplePackages) {
const packageDirectory = localPackages.get(packageName)
@@ -135,6 +122,30 @@ function validateExampleResolution(): string[] {
return violations
}
function validateAppResolution(): string[] {
const dependencies = readManifest('apps/cli/package.json').dependencies ?? {}
const references = pluginReferences.filter(reference => reference.file === 'apps/cli/cordis.yml')
return missingPluginDependencies(references, dependencies, 'apps/cli/package.json')
}
function missingPluginDependencies(
references: readonly PluginReference[],
dependencies: Readonly<Record<string, string>>,
manifestPath: string,
): string[] {
const requiredPackages = new Map<string, Set<string>>()
for (const reference of references) {
const packageName = packageNameFromSpecifier(reference.name)
if (packageName === undefined) continue
const locations = requiredPackages.get(packageName) ?? new Set<string>()
locations.add(reference.file)
requiredPackages.set(packageName, locations)
}
return [...requiredPackages].flatMap(([packageName, locations]) => packageName in dependencies
? []
: `${[...locations].join(', ')}: ${packageName} must be declared in ${manifestPath} dependencies`)
}
function readManifest(path: string): PackageManifest {
return JSON.parse(readFileSync(resolve(root, path), 'utf8')) as PackageManifest
}
@@ -177,7 +188,7 @@ function rootProjectReferences(): Set<string> {
}
function packageNameFromSpecifier(specifier: string): string | undefined {
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:')) return undefined
if (specifier.startsWith('.') || specifier.startsWith('/') || /^[a-z][a-z+.-]*:/i.test(specifier)) return undefined
const segments = specifier.split('/')
if (specifier.startsWith('@')) {
return segments.length >= 2 ? `${segments[0]}/${segments[1]}` : undefined

View File

@@ -47,6 +47,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/code-runtime/code-runtime-worker': { kind: 'indirect', reason: 'The worker backend delegates model rendering to Code Mode in dsh-tools.' },
'packages/client/hmr': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/modules': { kind: 'none', reason: 'Browser-side module-loading kernel machinery; registers no model surface.' },
'packages/client/test-runtime': { kind: 'none', reason: 'Browser-side test infrastructure (jsdom bench); registers no model surface.' },
'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.' },
@@ -58,6 +59,9 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/client/ui-slash': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-command': { kind: 'indirect', reason: 'The dispatch paths trigger the host command.execute RPC; each command handler\'s host package owns any model-visible effect.' },
'packages/client/ui-model': { kind: 'indirect', reason: 'Selection routes session.selectModel; the host snapshots the target at the next prompt-assembly boundary and owns the model-visible effect.' },
'packages/client/ui-goal': { kind: 'indirect', reason: 'The strip verbs route goal.* mutations; the host GoalService owns the model-visible goal/change context message.' },
'packages/client/ui-permission': { kind: 'indirect', reason: 'The picker submits the host /permission command; the knob events it appends own the model-visible effect through the sandbox/approval consumers.' },
'packages/client/ui-plan': { kind: 'indirect', reason: 'The chip dispatches /plan off; dsh-plan-mode owns the model-visible policy, exit tool, and logged state.' },
'packages/client/ui-question': { kind: 'indirect', reason: 'The package mounts dsh-tool-ask-user; that tool owns the model-visible schema and answer rendering.' },
'packages/client/ui-trajectory': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-workspace': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
@@ -73,6 +77,9 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'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/directory-picker': { kind: 'none', reason: 'The GUI-host picking seam registers no model surface.' },
'packages/host/directory-picker-browse': { kind: 'none', reason: 'The GUI-host picking backend registers no model surface.' },
'packages/host/directory-picker-native': { kind: 'none', reason: 'The GUI-host picking backend registers no model surface.' },
'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.' },
@@ -88,6 +95,8 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/sdk/sdk-client': { kind: 'none', reason: 'Client-process library; the model surface lives in the spawned runtime\'s composed plugins.' },
'packages/sdk/sdk-protocol': { kind: 'none', reason: 'Client-facing wire library; the runtime plugins behind the serving entry own the model surface.' },
'packages/sdk/telemetry': { kind: 'none', reason: 'The launcher-side reporter sends developer-cycle telemetry and registers no live agent or model surface.' },
'packages/session-projection/session-projection': { kind: 'none', reason: 'The projection registry serves client-facing read models of already-logged session state and registers no model surface.' },
'packages/session-projection/session-projection-cache': { kind: 'none', reason: 'The persisted cache accelerates host-side cold reads of projection state and registers no model surface.' },
'packages/session-query/session-query': { kind: 'none', reason: 'The trusted query service exposes cloned records only to callers and registers no model surface.' },
'packages/session-query/session-query-sqlite': { kind: 'none', reason: 'The search backend returns hits only to callers and registers no model surface.' },
'packages/telemetry/session-telemetry': { kind: 'none', reason: 'The seam observes the session stream and hands redacted copies outward; it registers no model surface.' },
@@ -112,6 +121,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/ui/user-interaction': { kind: 'indirect', reason: 'Model-facing consumers render provider answers and seam errors.' },
'packages/util/timeout': { kind: 'indirect', reason: 'Only timeout consumers render timeout outcomes.' },
'packages/util/retention': { kind: 'indirect', reason: 'Only retention consumers render retained content and omission metadata.' },
'packages/util/native-command': { kind: 'none', reason: 'The host-side subprocess runner registers no model surface.' },
'packages/web/web': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-web.' },
'packages/web/web-fetch-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-web.' },
'packages/web/web-search-exa': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-web.' },