Merge remote-tracking branch 'origin/master' into worktree/web-session-model-selector
# Conflicts: # .agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.i18n.yaml # .agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.i18n.yaml # apps/cli/src/web.ts # apps/web/tests/session-title.snapshot.ts # apps/web/tests/smoke-fixture.e2e.ts # packages/client/connection/src/client/api.ts # packages/client/connection/src/client/fixture.ts # packages/client/connection/src/client/index.ts # packages/client/runtime/src/client/index.ts # packages/client/runtime/src/client/sessions/conversation.ts # packages/client/runtime/src/client/sessions/session.ts # packages/client/runtime/tests/fake-api.ts # packages/client/ui-conversation/src/client/contract/slots.ts # packages/client/ui-conversation/src/client/index.ts # packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx # packages/client/ui-conversation/src/client/skeleton/InputBar.module.css # packages/client/ui-conversation/src/client/skeleton/InputBar.tsx # packages/client/ui-conversation/tests/chat-stats-bash-sample.spec.tsx # packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx # packages/client/ui-conversation/tests/chat-view.spec.tsx # packages/client/ui-conversation/tests/gate-branch-tails.spec.tsx # packages/client/ui-conversation/tests/skeleton-branches.spec.tsx # packages/client/ui-conversation/tests/skeleton.spec.tsx # packages/host/apiproxy/README.md # packages/host/apiproxy/src/api-proxy.ts # packages/host/apiproxy/src/api/rpc.schema.ts # packages/host/apiproxy/src/api/rpc.ts # packages/host/apiproxy/tests/api-proxy-models.spec.ts # packages/host/apiproxy/tests/rpc-schemas.spec.ts # packages/host/runtime/README.md # packages/llm/llm-deepseek/README.md # scripts/translation-pairing.manifest.json # tsconfig.client.json
This commit is contained in:
62
scripts/clean.spec.ts
Normal file
62
scripts/clean.spec.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { RepositoryCleaner } from './clean.ts'
|
||||
|
||||
const roots: string[] = []
|
||||
|
||||
function fixture(): string {
|
||||
const root = mkdtempSync(join(tmpdir(), 'dsh-clean-'))
|
||||
roots.push(root)
|
||||
return root
|
||||
}
|
||||
|
||||
function write(path: string, content = ''): void {
|
||||
mkdirSync(dirname(path), { recursive: true })
|
||||
writeFileSync(path, content)
|
||||
}
|
||||
|
||||
function addProject(root: string, path: string): void {
|
||||
write(join(root, 'tsconfig.json'), JSON.stringify({ files: [], references: [{ path }] }))
|
||||
write(join(root, path, 'tsconfig.json'), JSON.stringify({
|
||||
compilerOptions: { composite: true, outDir: 'lib/types' },
|
||||
include: ['src'],
|
||||
}))
|
||||
write(join(root, path, 'src/index.ts'), 'export {}\n')
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
describe('RepositoryCleaner', () => {
|
||||
it('derives live build outputs from project references and removes safe stale package residue', async () => {
|
||||
const root = fixture()
|
||||
addProject(root, 'products/shell')
|
||||
write(join(root, 'products/shell/lib/types/index.js'))
|
||||
write(join(root, 'products/shell/lib/index.js'))
|
||||
write(join(root, '.typecheck/legacy.tsbuildinfo'))
|
||||
write(join(root, 'root.tsbuildinfo'))
|
||||
write(join(root, 'packages/removed/ghost/node_modules/.bin/tool'))
|
||||
|
||||
await new RepositoryCleaner(root).clean()
|
||||
|
||||
expect(existsSync(join(root, 'products/shell/lib'))).toBe(false)
|
||||
expect(existsSync(join(root, 'products/shell/src/index.ts'))).toBe(true)
|
||||
expect(existsSync(join(root, '.typecheck'))).toBe(false)
|
||||
expect(existsSync(join(root, 'root.tsbuildinfo'))).toBe(false)
|
||||
expect(existsSync(join(root, 'packages/removed/ghost'))).toBe(false)
|
||||
})
|
||||
|
||||
it('does not delete any target when a manifest-less package contains an unknown file', async () => {
|
||||
const root = fixture()
|
||||
addProject(root, 'products/shell')
|
||||
write(join(root, 'products/shell/lib/types/index.js'))
|
||||
write(join(root, 'packages/removed/ghost/notes.txt'))
|
||||
|
||||
await expect(new RepositoryCleaner(root).clean()).rejects.toThrow('packages/removed/ghost/notes.txt')
|
||||
expect(existsSync(join(root, 'products/shell/lib'))).toBe(true)
|
||||
})
|
||||
})
|
||||
165
scripts/clean.ts
Normal file
165
scripts/clean.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { lstat, readdir, rm } from 'node:fs/promises'
|
||||
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import ts from 'typescript'
|
||||
import { repositoryConfigHost } from './ts-project.ts'
|
||||
|
||||
const knownOrphanEntries = new Set(['node_modules', 'lib', '.typecheck'])
|
||||
|
||||
function isMissing(error: unknown): boolean {
|
||||
return error instanceof Error && 'code' in error && error.code === 'ENOENT'
|
||||
}
|
||||
|
||||
async function exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await lstat(path)
|
||||
return true
|
||||
} catch (error) {
|
||||
if (isMissing(error)) return false
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function childDirectories(path: string): Promise<string[]> {
|
||||
try {
|
||||
const entries = await readdir(path, { withFileTypes: true })
|
||||
return entries.filter(entry => entry.isDirectory()).map(entry => join(path, entry.name))
|
||||
} catch (error) {
|
||||
if (isMissing(error)) return []
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
function repositoryPath(root: string, path: string): string {
|
||||
return relative(root, path).split(sep).join('/')
|
||||
}
|
||||
|
||||
function parseConfig(configPath: string): ts.ParsedCommandLine {
|
||||
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, repositoryConfigHost)
|
||||
if (!parsed) throw new Error(`clean: cannot parse TypeScript config ${configPath}`)
|
||||
if (parsed.errors.length > 0) {
|
||||
throw new Error(parsed.errors.map(error => ts.flattenDiagnosticMessageText(error.messageText, '\n')).join('\n'))
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
/** Plans and removes repository-owned build output without crossing the repository boundary. */
|
||||
export class RepositoryCleaner {
|
||||
constructor(private readonly root: string) {}
|
||||
|
||||
/**
|
||||
* Remove generated build state and package directories containing only known residue.
|
||||
* @returns Repository-relative paths that were removed.
|
||||
*/
|
||||
async clean(): Promise<string[]> {
|
||||
const targets = await this.plan()
|
||||
// Planning validates every target first, so an unsafe orphan prevents all deletion.
|
||||
for (const target of targets) await rm(target, { recursive: true, force: true })
|
||||
return targets.map(target => repositoryPath(this.root, target))
|
||||
}
|
||||
|
||||
private async plan(): Promise<string[]> {
|
||||
const targets = new Set<string>()
|
||||
const unsafeOrphans: string[] = []
|
||||
|
||||
// These checks cover legacy root-level incremental state emitted by older configs.
|
||||
await this.addIfPresent(targets, join(this.root, '.typecheck'))
|
||||
for (const entry of await readdir(this.root, { withFileTypes: true })) {
|
||||
if (entry.isFile() && entry.name.endsWith('.tsbuildinfo')) targets.add(join(this.root, entry.name))
|
||||
}
|
||||
|
||||
// The root project-reference graph is the source of truth for live build targets.
|
||||
// Each emitting project declares lib/types as outDir; its parent lib also owns
|
||||
// the sibling runtime bundles, so the complete build output root is removed.
|
||||
for (const outputDirectory of this.buildOutputDirectories()) {
|
||||
await this.addIfPresent(targets, outputDirectory)
|
||||
}
|
||||
|
||||
for (const groupDirectory of await childDirectories(join(this.root, 'packages'))) {
|
||||
for (const packageDirectory of await childDirectories(groupDirectory)) {
|
||||
// A package.json marks a live package; its output was discovered from the
|
||||
// project graph above, and its package-local node_modules must be preserved.
|
||||
if (await exists(join(packageDirectory, 'package.json'))) {
|
||||
continue
|
||||
}
|
||||
|
||||
// A manifest-less package directory is stale only when every remaining
|
||||
// entry is known generated residue; unknown files make the whole clean fail.
|
||||
const entries = await readdir(packageDirectory)
|
||||
const unknown = entries.filter(entry => !knownOrphanEntries.has(entry) && !entry.endsWith('.tsbuildinfo'))
|
||||
if (unknown.length > 0) {
|
||||
unsafeOrphans.push(...unknown.map(entry => repositoryPath(this.root, join(packageDirectory, entry))))
|
||||
} else {
|
||||
targets.add(packageDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unsafeOrphans.length > 0) {
|
||||
throw new Error([
|
||||
'clean: refusing to remove package directories without package.json; unknown entries remain:',
|
||||
...unsafeOrphans.sort().map(path => ` ${path}`),
|
||||
].join('\n'))
|
||||
}
|
||||
|
||||
return [...targets].sort()
|
||||
}
|
||||
|
||||
private buildOutputDirectories(): string[] {
|
||||
const outputs = new Set<string>()
|
||||
const pending = [join(this.root, 'tsconfig.json')]
|
||||
const visited = new Set<string>()
|
||||
|
||||
while (pending.length > 0) {
|
||||
const nextConfigPath = pending.pop()
|
||||
if (nextConfigPath === undefined) break
|
||||
const configPath = resolve(nextConfigPath)
|
||||
if (visited.has(configPath)) continue
|
||||
visited.add(configPath)
|
||||
|
||||
const parsed = parseConfig(configPath)
|
||||
if (parsed.options.outDir !== undefined) {
|
||||
const typesDirectory = resolve(parsed.options.outDir)
|
||||
if (basename(typesDirectory) !== 'types') {
|
||||
throw new Error(`clean: expected TypeScript outDir to end in /types: ${repositoryPath(this.root, typesDirectory)}`)
|
||||
}
|
||||
const outputDirectory = dirname(typesDirectory)
|
||||
this.assertRepositoryTarget(outputDirectory)
|
||||
outputs.add(outputDirectory)
|
||||
}
|
||||
|
||||
for (const reference of parsed.projectReferences ?? []) {
|
||||
pending.push(ts.resolveProjectReferencePath(reference))
|
||||
}
|
||||
}
|
||||
|
||||
return [...outputs]
|
||||
}
|
||||
|
||||
private assertRepositoryTarget(path: string): void {
|
||||
const repositoryRelative = relative(this.root, path)
|
||||
if (repositoryRelative === '' || repositoryRelative === '..' || repositoryRelative.startsWith(`..${sep}`) || isAbsolute(repositoryRelative)) {
|
||||
throw new Error(`clean: refusing build output outside repository: ${path}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async addIfPresent(targets: Set<string>, path: string): Promise<void> {
|
||||
// Missing outputs are normal on a clean checkout; only existing paths become deletion targets.
|
||||
if (await exists(path)) targets.add(path)
|
||||
}
|
||||
}
|
||||
|
||||
const scriptPath = fileURLToPath(import.meta.url)
|
||||
if (process.argv[1] !== undefined && resolve(process.argv[1]) === scriptPath) {
|
||||
try {
|
||||
const removed = await new RepositoryCleaner(resolve(dirname(scriptPath), '..')).clean()
|
||||
if (removed.length === 0) {
|
||||
console.log('clean: already clean')
|
||||
} else {
|
||||
console.log(`clean: removed ${removed.length} paths`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : error)
|
||||
process.exitCode = 1
|
||||
}
|
||||
}
|
||||
36
scripts/cordis-config-files.spec.ts
Normal file
36
scripts/cordis-config-files.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { cordisConfigFiles } from './cordis-config-files.ts'
|
||||
|
||||
const roots: string[] = []
|
||||
|
||||
afterEach(() => {
|
||||
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
describe('cordisConfigFiles', () => {
|
||||
it('finds Loader YAML without treating translation records as configs', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'dsh-cordis-config-files-'))
|
||||
roots.push(root)
|
||||
for (const directory of ['.claude', 'docs', 'examples', 'node_modules/pkg', 'vendor/pkg']) {
|
||||
mkdirSync(join(root, directory), { recursive: true })
|
||||
}
|
||||
for (const file of [
|
||||
'.claude/hidden.cordis.yml',
|
||||
'docs/cordis-primer.i18n.yaml',
|
||||
'examples/agent.cordis.yaml',
|
||||
'examples/headless.cordis.yml',
|
||||
'node_modules/pkg/hidden.cordis.yml',
|
||||
'vendor/pkg/hidden.cordis.yml',
|
||||
]) {
|
||||
writeFileSync(join(root, file), '[]\n')
|
||||
}
|
||||
|
||||
expect(cordisConfigFiles(root)).toEqual([
|
||||
join('examples', 'agent.cordis.yaml'),
|
||||
join('examples', 'headless.cordis.yml'),
|
||||
])
|
||||
})
|
||||
})
|
||||
18
scripts/cordis-config-files.ts
Normal file
18
scripts/cordis-config-files.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/** Cordis Loader configuration file discovery. */
|
||||
|
||||
import { globSync } from 'node:fs'
|
||||
|
||||
/**
|
||||
* Return repository-relative Cordis Loader YAML paths under `root`.
|
||||
*
|
||||
* Translation consistency records are YAML sidecars, never Loader inputs.
|
||||
*
|
||||
* @param root Repository root to scan.
|
||||
* @returns Sorted repository-relative Loader configuration paths.
|
||||
*/
|
||||
export function cordisConfigFiles(root: string): string[] {
|
||||
return globSync(['**/*cordis*.yml', '**/*cordis*.yaml'], {
|
||||
cwd: root,
|
||||
exclude: ['.claude/**', 'node_modules/**', 'vendor/**', '**/*.i18n.yaml'],
|
||||
}).sort()
|
||||
}
|
||||
@@ -7,7 +7,7 @@ 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', 'packages/examples/tui-demo/src/bin.ts', 'examples/tui-agent/code-mode.cordis.yml']],
|
||||
['tui', ['--import', 'tsx', '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']],
|
||||
])
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"docs/architecture.md": 1800,
|
||||
"docs/cordis-primer.md": 600,
|
||||
"docs/defensive-patterns.md": 550,
|
||||
"docs/testing.md": 1020,
|
||||
"docs/testing.md": 1100,
|
||||
"examples/AGENTS.md": 310,
|
||||
"packages/AGENTS.md": 660,
|
||||
"packages/README.md": 760
|
||||
"packages/README.md": 790
|
||||
}
|
||||
|
||||
@@ -30,8 +30,45 @@ function quote(value: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect exported interface and type shapes; omit names declared in multiple
|
||||
* packages rather than risk serving the wrong package's shape.
|
||||
* Reduce an exported class to its type shape: drop method/constructor bodies
|
||||
* and property initializers so the catalog serves member signatures, not
|
||||
* implementation.
|
||||
*/
|
||||
function classShape(node: ts.ClassDeclaration): ts.ClassDeclaration {
|
||||
const isNonPublic = (member: ts.ClassElement): boolean =>
|
||||
(ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined)?.some(m =>
|
||||
m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword) ?? false
|
||||
const members = node.members.flatMap((member): ts.ClassElement[] => {
|
||||
if (isNonPublic(member) || (ts.isPropertyDeclaration(member) && ts.isPrivateIdentifier(member.name))) return []
|
||||
if (ts.isMethodDeclaration(member)) {
|
||||
return [ts.factory.updateMethodDeclaration(
|
||||
member, member.modifiers, member.asteriskToken, member.name, member.questionToken,
|
||||
member.typeParameters, member.parameters, member.type, undefined)]
|
||||
}
|
||||
if (ts.isConstructorDeclaration(member)) {
|
||||
return [ts.factory.updateConstructorDeclaration(member, member.modifiers, member.parameters, undefined)]
|
||||
}
|
||||
if (ts.isGetAccessorDeclaration(member)) {
|
||||
return [ts.factory.updateGetAccessorDeclaration(
|
||||
member, member.modifiers, member.name, member.parameters, member.type, undefined)]
|
||||
}
|
||||
if (ts.isSetAccessorDeclaration(member)) {
|
||||
return [ts.factory.updateSetAccessorDeclaration(
|
||||
member, member.modifiers, member.name, member.parameters, undefined)]
|
||||
}
|
||||
if (ts.isPropertyDeclaration(member)) {
|
||||
return [ts.factory.updatePropertyDeclaration(
|
||||
member, member.modifiers, member.name, member.questionToken ?? member.exclamationToken, member.type, undefined)]
|
||||
}
|
||||
return [member]
|
||||
})
|
||||
return ts.factory.updateClassDeclaration(
|
||||
node, node.modifiers, node.name, node.typeParameters, node.heritageClauses, members)
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect exported interface, type-alias, and body-stripped class shapes; omit
|
||||
* names declared in multiple packages rather than serve the wrong shape.
|
||||
*/
|
||||
function collectTypeDecls(scanRoot: string = root): Map<string, string> {
|
||||
const printer = ts.createPrinter({ removeComments: true })
|
||||
@@ -41,14 +78,16 @@ function collectTypeDecls(scanRoot: string = root): Map<string, string> {
|
||||
const abs = resolve(scanRoot, rel)
|
||||
const sf = ts.createSourceFile(abs, readFileSync(abs, 'utf8'), ts.ScriptTarget.Latest, true)
|
||||
for (const stmt of sf.statements) {
|
||||
if (!ts.isInterfaceDeclaration(stmt) && !ts.isTypeAliasDeclaration(stmt)) continue
|
||||
const named = ts.isInterfaceDeclaration(stmt) || ts.isTypeAliasDeclaration(stmt) || ts.isClassDeclaration(stmt)
|
||||
if (!named || stmt.name === undefined) continue
|
||||
if (!(stmt.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false)) continue
|
||||
const name = stmt.name.text
|
||||
if (decls.has(name)) {
|
||||
ambiguous.add(name)
|
||||
continue
|
||||
}
|
||||
const printed = printer.printNode(ts.EmitHint.Unspecified, stmt, sf).replace(/\r/g, '')
|
||||
const emit = ts.isClassDeclaration(stmt) ? classShape(stmt) : stmt
|
||||
const printed = printer.printNode(ts.EmitHint.Unspecified, emit, sf).replace(/\r/g, '')
|
||||
decls.set(name, printed.length > MAX_DECL_CHARS
|
||||
? `${printed.slice(0, MAX_DECL_CHARS)} /* …truncated — full shape in source */`
|
||||
: printed)
|
||||
|
||||
@@ -35,6 +35,8 @@ export const LINK_MAP: Record<string, string> = {
|
||||
ContinuationDecision: 'core.md',
|
||||
ContinuationStop: 'core.md',
|
||||
GenerateOptions: 'core.md',
|
||||
AgentMessage: 'core.md',
|
||||
AgentMessageId: 'core.md',
|
||||
HookContext: 'core.md',
|
||||
LlmCallConfig: 'core.md',
|
||||
LlmModelContext: 'core.md',
|
||||
@@ -52,6 +54,7 @@ export const LINK_MAP: Record<string, string> = {
|
||||
SessionEvent: 'core.md',
|
||||
SessionId: 'core.md',
|
||||
SessionStartSource: 'core.md',
|
||||
SessionLogSnapshot: 'session-query.md',
|
||||
SessionSurfaceSnapshot: 'session-query.md',
|
||||
ApprovalOutcome: 'approval.md',
|
||||
ApprovalPolicy: 'approval.md',
|
||||
@@ -124,8 +127,10 @@ export const LINK_MAP: Record<string, string> = {
|
||||
SessionEventResultFilter: 'session-query.md',
|
||||
SessionEventSearchDocument: 'session-query.md',
|
||||
SessionEventSearchHit: 'session-query.md',
|
||||
SessionEventSearchPage: 'session-query.md',
|
||||
SessionEventSearchRequest: 'session-query.md',
|
||||
SessionEventTrace: 'session-query.md',
|
||||
SessionEventTraceObservation: 'session-query.md',
|
||||
SessionEventTraceRequest: 'session-query.md',
|
||||
SessionEventWindow: 'session-query.md',
|
||||
SessionLineageTrace: 'session-query.md',
|
||||
@@ -135,6 +140,8 @@ export const LINK_MAP: Record<string, string> = {
|
||||
SessionSearchHit: 'session-query.md',
|
||||
SessionSearchPage: 'session-query.md',
|
||||
SessionSearchRequest: 'session-query.md',
|
||||
SessionTitleObservation: 'session-query.md',
|
||||
SessionTitleObservationResult: 'session-query.md',
|
||||
SessionTitleProvider: 'session-title.md',
|
||||
SessionTitleSnapshot: 'session-title.md',
|
||||
SkillDefinition: 'skills.md',
|
||||
@@ -204,8 +211,17 @@ const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
|
||||
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',
|
||||
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',
|
||||
DomainFacility: 'domain form facility is owned by packages/storage/storage-domain/README.md',
|
||||
DomainImpl: 'domain implementation contract is owned by packages/storage/storage-domain/README.md',
|
||||
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',
|
||||
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',
|
||||
WebRoute: 'route registration contract is owned by packages/host/webserver/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',
|
||||
TuiOverlayRequest: 'service-local extension contract is owned by packages/ui/tui/README.md',
|
||||
@@ -221,6 +237,8 @@ const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
|
||||
WorkflowAgentEndInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts',
|
||||
WorkflowAgentInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts',
|
||||
WorkflowResultInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts',
|
||||
Workspace: 'workspace entity contract is owned by packages/workspace/workspace/README.md',
|
||||
WorkspaceId: 'branded id is owned by packages/workspace/workspace/README.md',
|
||||
}
|
||||
|
||||
/** Collect named references from parameter, generic-constraint/default, and return types. */
|
||||
|
||||
@@ -77,7 +77,10 @@ const GROUP_ORDER = [
|
||||
'session-persistence',
|
||||
'session-query',
|
||||
'session-title',
|
||||
'storage',
|
||||
'workspace',
|
||||
'support',
|
||||
'acp',
|
||||
'ui',
|
||||
]
|
||||
|
||||
@@ -129,24 +132,49 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
title: 'Durable session persistence seam',
|
||||
mode: 'seam',
|
||||
implementations: ['session-persistence-jsonl', 'session-persistence-sqlite'],
|
||||
consumers: ['agent-loop', 'tool-bash', 'hooks-claude', 'hooks-codex', 'acp', 'session-query', 'session-query-sqlite'],
|
||||
consumers: ['agent-loop', 'tool-bash', 'hooks-claude', 'hooks-codex', 'session-query', 'session-query-sqlite'],
|
||||
note: 'Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time.',
|
||||
},
|
||||
{
|
||||
key: 'storage',
|
||||
pkg: 'storage',
|
||||
title: 'Non-session storage hub',
|
||||
mode: 'seam',
|
||||
implementations: ['storage-json', 'storage-sqlite'],
|
||||
consumers: ['storage-domain'],
|
||||
note: 'Backends register side by side under names; data forms (domain first) mount on the hub and translate typed operations into opaque KV-unit primitives.',
|
||||
},
|
||||
{
|
||||
key: 'storageDomain',
|
||||
pkg: 'storage-domain',
|
||||
title: 'Domain data facility',
|
||||
mode: 'core',
|
||||
consumers: ['workspace'],
|
||||
note: 'Waits for every configured backend, then publishes the domain form as one lifecycle-bound service for typed durable state.',
|
||||
},
|
||||
{
|
||||
key: 'workspace',
|
||||
pkg: 'workspace',
|
||||
title: 'Workspace entity registry',
|
||||
mode: 'core',
|
||||
consumers: ['apiproxy'],
|
||||
note: 'Owns WorkspaceId-branded records over the domain facility; stable sessionIds accounts drive Host RPC and GUI projections.',
|
||||
},
|
||||
{
|
||||
key: 'sessionQuery',
|
||||
pkg: 'session-query',
|
||||
title: 'Session reads, traces, filters, and search',
|
||||
mode: 'seam',
|
||||
implementations: ['session-query-sqlite'],
|
||||
consumers: ['session-reference'],
|
||||
note: 'The interface supplies exact reads, filters, and traces; its concrete backend adds full-text reconciliation, ranking, snippets, and cursor generations on the same service.',
|
||||
consumers: ['session-reference', 'tool-session-query'],
|
||||
note: 'The interface supplies exact reads, filters, and traces; its concrete backend adds full-text reconciliation, ranking, snippets, and cursor generations, while the model consumer owns workspace authority and cursor-free rendering.',
|
||||
},
|
||||
{
|
||||
key: 'sessionReferences',
|
||||
pkg: 'session-reference',
|
||||
title: 'Cross-session snapshot preparation',
|
||||
mode: 'core',
|
||||
consumers: ['tui', 'acp'],
|
||||
consumers: ['tui'],
|
||||
note: 'Projects bounded current-surface conversation snapshots into durable untrusted message context; host adapters own mention syntax.',
|
||||
},
|
||||
{
|
||||
@@ -170,7 +198,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
pkg: 'tools',
|
||||
title: 'Tool registry and guarded execution pipeline',
|
||||
mode: 'core',
|
||||
consumers: ['agent-loop', 'tool-ask-user', 'tool-bash', 'tool-cordis', 'tool-fs', 'tool-pty', 'tool-skill', 'tool-subagent', 'tool-todo', 'tool-web', 'acp'],
|
||||
consumers: ['agent-loop', 'tool-ask-user', 'tool-bash', 'tool-cordis', 'tool-fs', 'tool-pty', 'tool-skill', 'tool-subagent', 'tool-todo', 'tool-web'],
|
||||
note: 'Registers capabilities, owns Code Mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation.',
|
||||
},
|
||||
{
|
||||
@@ -178,8 +206,8 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
pkg: 'user-interaction',
|
||||
title: 'Human question/answer seam',
|
||||
mode: 'seam',
|
||||
implementations: ['tui', 'acp'],
|
||||
consumers: ['tool-ask-user', 'tui', 'acp'],
|
||||
implementations: ['tui'],
|
||||
consumers: ['tool-ask-user', 'tui'],
|
||||
note: 'UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise.',
|
||||
},
|
||||
{
|
||||
@@ -187,7 +215,6 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
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.',
|
||||
},
|
||||
{
|
||||
@@ -195,8 +222,8 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
pkg: 'commands',
|
||||
title: 'Human command registry',
|
||||
mode: 'core',
|
||||
consumers: ['tui', 'acp'],
|
||||
note: 'Plugins register direct human commands; TUI and ACP consume the same effective per-agent catalog without sending invocations to the model.',
|
||||
consumers: ['tui'],
|
||||
note: 'Plugins register direct human commands; TUI consumes the effective per-agent catalog without sending invocations to the model.',
|
||||
},
|
||||
{
|
||||
key: 'tui',
|
||||
@@ -295,7 +322,6 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
title: 'Permission presets',
|
||||
mode: 'core',
|
||||
implementations: [],
|
||||
consumers: ['acp'],
|
||||
note: 'User-facing preset table (`workspace-write`/`danger-full-access`) bundling the sandbox-mode and approval-policy knobs; a switch writes one `permission/preset` event through to both knob events.',
|
||||
},
|
||||
{
|
||||
@@ -361,6 +387,22 @@ 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: 'httpServer',
|
||||
pkg: 'webserver',
|
||||
title: 'HTTP route registration',
|
||||
mode: 'core',
|
||||
consumers: ['connection', 'modules', 'hmr'],
|
||||
note: 'Plain node:http carrier: named-route registry, index transform taps, and the static dist fallback; web-transport plugins register their own routes.',
|
||||
},
|
||||
{
|
||||
key: 'clientModuleHost',
|
||||
pkg: 'modules',
|
||||
title: 'Client plugin graph host',
|
||||
mode: 'core',
|
||||
consumers: ['hmr'],
|
||||
note: 'Composes the __DSH_BOOT__ entry graph from an incremental dshClient scan, serves plugin bundles, and notifies rebuilt/graph-changed subscribers.',
|
||||
},
|
||||
{
|
||||
key: 'workflows',
|
||||
pkg: 'workflow',
|
||||
@@ -531,10 +573,10 @@ const APP_EXAMPLES = [
|
||||
{
|
||||
id: 'acp',
|
||||
rel: 'examples/acp-agent/composition.md',
|
||||
title: 'ACP Agent App Composition',
|
||||
title: 'ACP Automation App Composition',
|
||||
label: 'examples/acp-agent',
|
||||
config: 'examples/acp-agent/cordis.yml',
|
||||
summary: 'The ACP demo exposes the same agent spine over JSON-RPC stdio, with no stdout logger and no pre-created agent; clients create sessions through the ACP bridge.',
|
||||
summary: 'The ACP demo exposes fresh baseline-prompt agent sessions to programmatic clients over JSON-RPC stdio, with no stdout logger, human UI, or pre-created agent.',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -550,7 +592,7 @@ function renderAppExpansion(lines: string[], appNode: string, pluginName: string
|
||||
} else if (pluginName === '@deepseek-ai/dsh-cli-demo') {
|
||||
lines.push(` ${appNode} --> ${nodeId('frontdoor', 'cli')}["one-shot driver<br/>format-pure stdout<br/>fresh top-level agent"]`)
|
||||
} else if (pluginName === '@deepseek-ai/dsh-acp-demo') {
|
||||
lines.push(` ${appNode} --> ${nodeId('frontdoor', 'acp')}["@deepseek-ai/dsh-acp<br/>JSON-RPC stdio bridge<br/>sessions created by client"]`)
|
||||
lines.push(` ${appNode} --> ${nodeId('frontdoor', 'acp')}["@deepseek-ai/dsh-acp<br/>automation-only JSON-RPC stdio<br/>fresh sessions created by client"]`)
|
||||
}
|
||||
lines.push(
|
||||
` ${agentCore} --> ${nodeId('spine', 'llm')}["ctx.llm"]`,
|
||||
@@ -918,8 +960,8 @@ function renderLifecycle(): string {
|
||||
' participant Session',
|
||||
' participant Persistence',
|
||||
' participant SDK as UI or SDK listener',
|
||||
' User->>Agent: send(content)',
|
||||
` Agent-->>SDK: ${mermaidCode('agent/queued')}`,
|
||||
' User->>Agent: followup(content)',
|
||||
` Agent-->>SDK: ${mermaidCode('agent/inbox/enqueue')}`,
|
||||
' Agent->>Driver: queued work wakes driver',
|
||||
` Driver-->>SDK: ${mermaidCode('agent/status')} running`,
|
||||
` Driver->>Session: ${mermaidCode('turn/start')}`,
|
||||
@@ -998,7 +1040,7 @@ function renderToolPipeline(): string {
|
||||
' normalized["Registry outer normalization<br/>pipeline/result snapshot throws become isError"]',
|
||||
' finalize["ToolDefinition.finalizeContent<br/>last content-only invariant"]',
|
||||
` final["${mermaidCode('tools/result')} synchronous notification<br/>frozen authoritative outcome"]`,
|
||||
' context["Active-batch additionalContexts FIFO<br/>context/message after recorded tool results"]',
|
||||
' context["Active-batch additionalContexts FIFO<br/>injected user/message after recorded tool results"]',
|
||||
` toolResult["Session event: ${mermaidCode('tool/result')}<br/>single model-facing outcome"]`,
|
||||
' allResults["Tool batch settled<br/>recorded tool/result events complete"]',
|
||||
' presentResult["UI completed card<br/>presentResult(args, result)"]',
|
||||
@@ -1039,35 +1081,6 @@ function renderToolPipeline(): string {
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
function renderSnapshotReplay(): string {
|
||||
const maintenance = 'curated Mermaid sequence based on the snapshot test harness'
|
||||
return [
|
||||
...generatedHeader('ACP Snapshot Replay'),
|
||||
'This graph explains what a snapshot scenario proves: recorded real-model session logs are replayed keylessly, ACP stdout is normalized and diffed, and scenario workspaces preserve tool side effects that the UI stream alone cannot prove.',
|
||||
'',
|
||||
'```mermaid',
|
||||
'sequenceDiagram',
|
||||
' participant Recorder as Real API recording',
|
||||
' participant Fixture as snapshot fixture',
|
||||
' participant Workspace',
|
||||
' participant Replay as llm-replay adapter',
|
||||
' participant ACP as acp-agent subprocess',
|
||||
' participant Expected as stdout expected output',
|
||||
' Recorder->>Fixture: session.jsonl + workspace inputs',
|
||||
' Fixture->>Workspace: seed files and hook configs',
|
||||
' Fixture->>Replay: recorded StreamChunk script',
|
||||
` Replay->>ACP: deterministic ${mermaidCode('llm/stream')} chunks`,
|
||||
' ACP->>Workspace: bash, fs, and hook side effects',
|
||||
' ACP->>Expected: normalized sessionUpdate stream',
|
||||
' Expected-->>ACP: diff must be empty',
|
||||
'```',
|
||||
'',
|
||||
'The fs and hook snapshot matrix is valuable because it proves world state, hook decisions, and failed tool-card rendering, not just that replay returns text.',
|
||||
'',
|
||||
...maintenanceFooter(maintenance),
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
function renderDocs(): GraphDoc[] {
|
||||
const pkgs = collectPackageGraph(root, GROUP_ORDER, 'gen-doc-graphs')
|
||||
const docs: GraphDoc[] = [
|
||||
@@ -1076,7 +1089,6 @@ function renderDocs(): GraphDoc[] {
|
||||
{ rel: 'docs/event-producer-consumer.md', content: renderEventRelations(pkgs) },
|
||||
{ rel: 'docs/agent-lifecycle.md', content: renderLifecycle() },
|
||||
{ rel: 'docs/tool-execution-pipeline.md', content: renderToolPipeline() },
|
||||
{ rel: 'packages/ui/acp/snapshot-replay.md', content: renderSnapshotReplay() },
|
||||
]
|
||||
docs.unshift({ rel: 'docs/graph-atlas.md', content: renderIndex(docs) })
|
||||
return docs
|
||||
@@ -1092,7 +1104,6 @@ function renderIndex(docs: GraphDoc[]): string {
|
||||
'docs/event-producer-consumer.md': 'event producer/consumer matrix',
|
||||
'docs/agent-lifecycle.md': 'agent turn and step lifecycle',
|
||||
'docs/tool-execution-pipeline.md': 'tool execution pipeline',
|
||||
'packages/ui/acp/snapshot-replay.md': 'ACP snapshot replay',
|
||||
}
|
||||
const modes: Record<string, string> = {
|
||||
'docs/capability-seams.md': 'hybrid generated',
|
||||
@@ -1103,7 +1114,6 @@ function renderIndex(docs: GraphDoc[]): string {
|
||||
'docs/event-producer-consumer.md': 'hybrid generated',
|
||||
'docs/agent-lifecycle.md': 'curated',
|
||||
'docs/tool-execution-pipeline.md': 'curated',
|
||||
'packages/ui/acp/snapshot-replay.md': 'curated',
|
||||
}
|
||||
const rows = [
|
||||
'| [module dependency graph](module-graph.md) | `generated` |',
|
||||
|
||||
@@ -38,6 +38,7 @@ const GROUP_ORDER = [
|
||||
'session-query',
|
||||
'session-title',
|
||||
'support',
|
||||
'acp',
|
||||
'ui',
|
||||
]
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import { basename, resolve } from 'node:path'
|
||||
import { Context } from 'cordis'
|
||||
import type { ToolSchema } from '@deepseek-ai/dsh-llm'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SessionQuerySqlite from '@deepseek-ai/dsh-session-query-sqlite'
|
||||
import GoalService from '@deepseek-ai/dsh-goal'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools'
|
||||
@@ -39,6 +41,7 @@ import * as ToolGoal from '@deepseek-ai/dsh-tool-goal'
|
||||
import Lsp from '@deepseek-ai/dsh-lsp'
|
||||
import * as ToolLsp from '@deepseek-ai/dsh-tool-lsp'
|
||||
import * as ToolSkill from '@deepseek-ai/dsh-tool-skill'
|
||||
import * as ToolSessionQuery from '@deepseek-ai/dsh-tool-session-query'
|
||||
import * as ToolTasks from '@deepseek-ai/dsh-tool-tasks'
|
||||
import * as ToolTodo from '@deepseek-ai/dsh-tool-todo'
|
||||
import * as ToolSubagent from '@deepseek-ai/dsh-tool-subagent'
|
||||
@@ -263,7 +266,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
dir: 'tool-goal',
|
||||
source: 'packages/goal/tool-goal/src/index.ts',
|
||||
requires: ['ctx.tools', 'ctx.agents', 'ctx.goals', 'ctx.systemPrompt', 'a calling Agent in an authorized open turn'],
|
||||
writes: ['tool/call', 'context/message goal snapshot for mutations', 'tool/result'],
|
||||
writes: ['tool/call', 'user/message goal snapshot for mutations', 'tool/result'],
|
||||
async mount(ctx) {
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(GoalService)
|
||||
@@ -316,6 +319,20 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
await ctx.plugin(ToolSkill)
|
||||
},
|
||||
},
|
||||
{
|
||||
pkg: '@deepseek-ai/dsh-tool-session-query',
|
||||
dir: 'tool-session-query',
|
||||
source: 'packages/session-query/tool-session-query/src/index.ts',
|
||||
requires: ['ctx.tools', 'ctx.systemPrompt', 'ctx.sessionQuery', 'a calling Agent for workspace authority'],
|
||||
writes: ['tool/call', 'tool/result'],
|
||||
async mount(ctx) {
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionQuerySqlite, { path: ':memory:' })
|
||||
await ctx.plugin(ToolSessionQuery)
|
||||
},
|
||||
note:
|
||||
'The five read-only tools hide provider cursors and authorize every result from the immutable calling agent session. The package is opt-in; compositions that need enforced deadlines or bounded inline output also mount the generic timeout or spill policies.',
|
||||
},
|
||||
{
|
||||
pkg: '@deepseek-ai/dsh-tool-subagent',
|
||||
dir: 'tool-subagent',
|
||||
@@ -336,7 +353,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
dir: 'tool-tasks',
|
||||
source: 'packages/tasks/tool-tasks/src/index.ts',
|
||||
requires: ['ctx.tools', 'ctx.tasks', 'ctx.systemPrompt'],
|
||||
writes: ['tool/call', 'tool/result', 'context/message via agent.inject() for background completion notices'],
|
||||
writes: ['tool/call', 'tool/result', 'user/message via agent.inject() for background completion notices'],
|
||||
async mount(ctx) {
|
||||
await ctx.plugin(TaskService)
|
||||
await ctx.plugin(ToolTasks)
|
||||
@@ -354,7 +371,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
await ctx.plugin(ToolTodo)
|
||||
},
|
||||
note:
|
||||
'todo_write is session-owned state; UIs render the latest todo/write event as a checklist or ACP plan.',
|
||||
'todo_write is session-owned state; UIs render the latest todo/write event as a checklist.',
|
||||
},
|
||||
{
|
||||
pkg: '@deepseek-ai/dsh-tool-workflow',
|
||||
|
||||
@@ -172,13 +172,13 @@ describe('rewriteMarkdown', () => {
|
||||
})
|
||||
|
||||
describe('docsPages locale routes', () => {
|
||||
it('publishes every route in both locales and selects paired user sources', () => {
|
||||
it('publishes every route in both locales and selects paired sources', () => {
|
||||
const byRoute = new Map(docsPages.map(page => [page.route, page]))
|
||||
for (const page of docsPages.filter(page => page.locale === 'root')) {
|
||||
const counterpart = byRoute.get(`en/${page.route}`)
|
||||
expect(counterpart, page.route).toBeDefined()
|
||||
expect(counterpart?.locale).toBe('en')
|
||||
if (page.source.startsWith('docs/user/')) {
|
||||
if (page.contentLocale === 'zh-CN') {
|
||||
expect(page.source).toMatch(/\.zh\.md$/)
|
||||
expect(page.contentLocale).toBe('zh-CN')
|
||||
expect(counterpart?.source).toBe(page.source.replace(/\.zh\.md$/, '.md'))
|
||||
@@ -190,6 +190,22 @@ describe('docsPages locale routes', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('projects translated core-data pages while retaining explicit English fallbacks', () => {
|
||||
const rootPages = docsPages.filter(page => (
|
||||
page.locale === 'root' && page.route.startsWith('reference/core-data-structures/')
|
||||
))
|
||||
const translated = rootPages.filter(page => page.contentLocale === 'zh-CN')
|
||||
const fallbacks = rootPages.filter(page => page.contentLocale === 'en-US')
|
||||
|
||||
expect(translated).toHaveLength(18)
|
||||
expect(translated.every(page => page.source.endsWith('.zh.md'))).toBe(true)
|
||||
expect(fallbacks.map(page => page.source).sort()).toEqual([
|
||||
'docs/core-data-structures/commands.md',
|
||||
'docs/core-data-structures/goal.md',
|
||||
'docs/core-data-structures/pty.md',
|
||||
])
|
||||
})
|
||||
|
||||
it('publishes the Cordis core API under matching locale structures', () => {
|
||||
const files = ['context.md', 'events.md', 'fiber.md', 'registry.md', 'service.md']
|
||||
for (const file of files) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,25 +1,196 @@
|
||||
{
|
||||
"requiredSince": "2026-07-14",
|
||||
"required": [
|
||||
".agents/notes/README.md",
|
||||
".agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md",
|
||||
".agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md",
|
||||
".agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.md",
|
||||
".agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md",
|
||||
".agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.md",
|
||||
".agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.md",
|
||||
".agents/notes/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.md",
|
||||
".agents/notes/implemented/architecture/2026-06-13-capability-seams.md",
|
||||
".agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md",
|
||||
".agents/notes/implemented/architecture/2026-06-14-session-persistence.md",
|
||||
".agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.md",
|
||||
".agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md",
|
||||
".agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md",
|
||||
".agents/notes/implemented/architecture/2026-06-18-session-surface.md",
|
||||
".agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md",
|
||||
".agents/notes/implemented/architecture/2026-06-20-branded-ids.md",
|
||||
".agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.md",
|
||||
".agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md",
|
||||
".agents/notes/implemented/architecture/2026-06-20-package-hierarchy.md",
|
||||
".agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md",
|
||||
".agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md",
|
||||
".agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.md",
|
||||
".agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md",
|
||||
".agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.md",
|
||||
".agents/notes/implemented/architecture/2026-07-02-fs-per-session-cwd.md",
|
||||
".agents/notes/implemented/architecture/2026-07-02-result-time-applied-hunk-diffs.md",
|
||||
".agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md",
|
||||
".agents/notes/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.md",
|
||||
".agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md",
|
||||
".agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md",
|
||||
".agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.md",
|
||||
".agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md",
|
||||
".agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.md",
|
||||
".agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md",
|
||||
".agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md",
|
||||
".agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.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/architecture/2026-07-23-client-plugin-loading-model.md",
|
||||
".agents/notes/implemented/feature/2026-07-24-web-session-model-selector.md",
|
||||
".agents/notes/implemented/feature/2026-06-14-acp-multi-session.md",
|
||||
".agents/notes/implemented/feature/2026-06-15-code-mode.md",
|
||||
".agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.md",
|
||||
".agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md",
|
||||
".agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md",
|
||||
".agents/notes/implemented/feature/2026-06-22-acp-subagent-backend.md",
|
||||
".agents/notes/implemented/feature/2026-06-25-ask-user-question.md",
|
||||
".agents/notes/implemented/feature/2026-06-29-todo-write-tool.md",
|
||||
".agents/notes/implemented/feature/2026-06-30-hook-bridges.md",
|
||||
".agents/notes/implemented/feature/2026-06-30-hook-protocol-lib.md",
|
||||
".agents/notes/implemented/feature/2026-06-30-interception-seams.md",
|
||||
".agents/notes/implemented/feature/2026-06-30-session-store-fork-api.md",
|
||||
".agents/notes/implemented/feature/2026-06-30-subagent-observe-enrich.md",
|
||||
".agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md",
|
||||
".agents/notes/implemented/feature/2026-07-05-skill-system.md",
|
||||
".agents/notes/implemented/feature/2026-07-06-approval-seam.md",
|
||||
".agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md",
|
||||
".agents/notes/implemented/feature/2026-07-06-sandbox.md",
|
||||
".agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md",
|
||||
".agents/notes/implemented/feature/2026-07-07-session-prefix.md",
|
||||
".agents/notes/implemented/feature/2026-07-08-repeat-tool-guard.md",
|
||||
".agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md",
|
||||
".agents/notes/implemented/feature/2026-07-10-session-query-service.md",
|
||||
".agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md",
|
||||
".agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.md",
|
||||
".agents/notes/implemented/process/2026-06-11-quality-gates.md",
|
||||
".agents/notes/implemented/process/2026-06-11-tsdown-over-dumble.md",
|
||||
".agents/notes/implemented/process/2026-06-11-vendor-cordis-as-source.md",
|
||||
".agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.md",
|
||||
".agents/notes/implemented/process/2026-06-17-ts-build-config.md",
|
||||
".agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md",
|
||||
".agents/notes/implemented/process/2026-06-20-agent-note-classification.md",
|
||||
".agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md",
|
||||
".agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.md",
|
||||
".agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md",
|
||||
".agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md",
|
||||
".agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.md",
|
||||
".agents/notes/implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md",
|
||||
".agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.md",
|
||||
".agents/notes/implemented/process/2026-07-04-persistence-log-catalog.md",
|
||||
".agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.md",
|
||||
".agents/notes/implemented/process/2026-07-06-export-surface-jsdoc-gate.md",
|
||||
".agents/notes/implemented/process/2026-07-06-generated-config-catalog.md",
|
||||
".agents/notes/implemented/process/2026-07-06-node-engine-floor.md",
|
||||
".agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md",
|
||||
".agents/notes/implemented/process/2026-07-10-readme-known-limitations-gate.md",
|
||||
".agents/notes/implemented/process/2026-07-12-package-model-experience-contract.md",
|
||||
".agents/notes/implemented/process/2026-07-19-web-styling-system.md",
|
||||
".agents/notes/implemented/simplification/2026-06-19-drop-mutable-session-summary.md",
|
||||
".agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.md",
|
||||
".agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md",
|
||||
".agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.md",
|
||||
".agents/notes/implemented/simplification/2026-06-20-prune-dead-seam-methods.md",
|
||||
".agents/notes/implemented/simplification/2026-06-20-public-agent-stop-surface.md",
|
||||
".agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.md",
|
||||
".agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.md",
|
||||
".agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md",
|
||||
".agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.md",
|
||||
".agents/notes/implemented/simplification/2026-07-04-drop-image-content-block.md",
|
||||
".agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.md",
|
||||
".agents/notes/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md",
|
||||
".agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.md",
|
||||
".agents/notes/implemented/simplification/2026-07-04-prune-write-only-fs-surface.md",
|
||||
".agents/notes/implemented/simplification/2026-07-04-remove-agent-steering-mirror.md",
|
||||
".agents/notes/implemented/simplification/2026-07-04-share-app-bin-boot-glue.md",
|
||||
".agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.md",
|
||||
".agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md",
|
||||
".agents/notes/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md",
|
||||
".agents/notes/implemented/simplification/2026-07-12-prune-unused-web-seam-fields.md",
|
||||
".agents/notes/implemented/simplification/2026-07-12-simplify-session-log-representation.md",
|
||||
".agents/notes/implemented/testing/2026-06-11-property-based-testing.md",
|
||||
".agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md",
|
||||
".agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.md",
|
||||
".agents/notes/implemented/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md",
|
||||
".agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md",
|
||||
".agents/notes/implemented/testing/2026-06-22-fork-snapshot-scenarios.md",
|
||||
".agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.md",
|
||||
".agents/notes/implemented/testing/2026-07-04-hook-snapshot-matrix.md",
|
||||
".agents/notes/implemented/testing/2026-07-04-single-source-acp-replay-config.md",
|
||||
".agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.md",
|
||||
".agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.md",
|
||||
".agents/notes/proposed/architecture/2026-06-16-typed-event-schemas.md",
|
||||
".agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.md",
|
||||
".agents/notes/proposed/feature/2026-07-07-claude-code-and-codex-subagent-backends.md",
|
||||
".agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.md",
|
||||
".agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md",
|
||||
".agents/notes/rejected/feature/2026-07-13-stream-workflow-progress-through-tool-calls.md",
|
||||
".agents/notes/proposed/process/2026-06-11-api-extractor-reports.md",
|
||||
".agents/notes/proposed/process/2026-06-11-architectural-conformance.md",
|
||||
".agents/notes/proposed/process/2026-06-11-supply-chain-and-vendor-drift.md",
|
||||
".agents/notes/proposed/process/2026-06-20-discover-package-inventory.md",
|
||||
".agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md",
|
||||
".agents/notes/proposed/testing/2026-06-11-deterministic-and-stress-testing.md",
|
||||
".agents/notes/proposed/testing/2026-06-11-mutation-testing.md",
|
||||
".agents/notes/rejected/architecture/2026-06-11-immutable-public-surfaces.md",
|
||||
".agents/notes/rejected/architecture/2026-06-20-providerless-example-base.md",
|
||||
".agents/notes/rejected/process/2026-07-04-generate-agent-note-index-tables.md",
|
||||
".agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.md",
|
||||
".agents/notes/rejected/simplification/2026-06-20-drop-acp-session-load.md",
|
||||
".agents/notes/rejected/simplification/2026-06-20-drop-acp-terminal-meta.md",
|
||||
".agents/notes/rejected/simplification/2026-06-20-drop-bash-output-spill-files.md",
|
||||
".agents/notes/rejected/simplification/2026-06-20-drop-durable-step-boundaries.md",
|
||||
".agents/notes/rejected/simplification/2026-06-20-drop-unused-session-lineage.md",
|
||||
".agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.md",
|
||||
".agents/notes/rejected/simplification/2026-06-20-generic-tool-rendering.md",
|
||||
".agents/notes/rejected/simplification/2026-06-20-retire-mid-turn-steering.md",
|
||||
".agents/notes/rejected/simplification/2026-06-20-single-session-acp-bridge.md",
|
||||
".agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md",
|
||||
".agents/notes/rejected/simplification/2026-07-04-prune-unimplemented-subagent-vocabulary.md",
|
||||
".agents/notes/rejected/simplification/2026-07-12-collapse-workflow-to-foreground-core.md",
|
||||
".agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-surface.md",
|
||||
"README.md",
|
||||
"docs/architecture.md",
|
||||
"docs/cookbook/adding-a-package.md",
|
||||
"docs/cookbook/adding-a-tool.md",
|
||||
"docs/cookbook/adding-a-vendored-package.md",
|
||||
"docs/cookbook/adding-an-llm-adapter.md",
|
||||
"docs/cookbook/extension-cookbook.md",
|
||||
"docs/cookbook/responding-to-pr-review-on-a-stack.md",
|
||||
"docs/cordis-primer.md",
|
||||
"docs/core-data-structures/approval.md",
|
||||
"docs/core-data-structures/bash.md",
|
||||
"docs/core-data-structures/code-runtime.md",
|
||||
"docs/core-data-structures/compaction.md",
|
||||
"docs/core-data-structures/core.md",
|
||||
"docs/core-data-structures/filesystem.md",
|
||||
"docs/core-data-structures/llm-streaming.md",
|
||||
"docs/core-data-structures/persistence.md",
|
||||
"docs/core-data-structures/sandbox.md",
|
||||
"docs/core-data-structures/scope.md",
|
||||
"docs/core-data-structures/session-query.md",
|
||||
"docs/core-data-structures/session.md",
|
||||
"docs/core-data-structures/skills.md",
|
||||
"docs/core-data-structures/subagent.md",
|
||||
"docs/core-data-structures/system-prompt.md",
|
||||
"docs/core-data-structures/tools.md",
|
||||
"docs/core-data-structures/user-interaction.md",
|
||||
"docs/core-data-structures/web.md",
|
||||
"docs/core-data-structures/workflow.md",
|
||||
"docs/defensive-patterns.md",
|
||||
"docs/development.md",
|
||||
"docs/glossary.md",
|
||||
"docs/i18n/README.md",
|
||||
"docs/i18n/translation-rules.md",
|
||||
"docs/postmortem/0001-acp-default-export-drops-inject.md",
|
||||
"docs/postmortem/0002-js-expression-disabled-filesystem-tools.md",
|
||||
"docs/postmortem/README.md",
|
||||
"docs/testing.md",
|
||||
"docs/user/develop/basic/config.md",
|
||||
"docs/user/develop/basic/index.md",
|
||||
"docs/user/develop/basic/tool.md",
|
||||
@@ -40,14 +211,19 @@
|
||||
".agents/notes/AGENTS.md",
|
||||
".agents/notes/implemented/AGENTS.md",
|
||||
"docs/AGENTS.md",
|
||||
"docs/agent-lifecycle.md",
|
||||
"docs/capability-seams.md",
|
||||
"docs/config-catalog.md",
|
||||
"docs/cordis-catalog/",
|
||||
"docs/event-producer-consumer.md",
|
||||
"docs/graph-atlas.md",
|
||||
"docs/i18n/style-samples.md",
|
||||
"docs/i18n/terminology.md",
|
||||
"docs/i18n/translation-prompt.md",
|
||||
"docs/module-graph.md",
|
||||
"docs/persistence-catalog.md",
|
||||
"docs/tool-catalog.md",
|
||||
"docs/tool-execution-pipeline.md",
|
||||
"python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ interface ProjectGraph {
|
||||
options: ts.CompilerOptions
|
||||
}
|
||||
|
||||
const configHost: ts.ParseConfigFileHost = {
|
||||
/** TypeScript config host shared by repository scripts. */
|
||||
export const repositoryConfigHost: ts.ParseConfigFileHost = {
|
||||
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
|
||||
readDirectory: (...args) => ts.sys.readDirectory(...args),
|
||||
fileExists: fileName => ts.sys.fileExists(fileName),
|
||||
@@ -52,7 +53,7 @@ function loadProjectGraph(projectRoot: string): ProjectGraph {
|
||||
|
||||
/** Parse one config file and fail loud on any config diagnostic. */
|
||||
function parseConfig(configPath: string): ts.ParsedCommandLine {
|
||||
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, configHost)
|
||||
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, repositoryConfigHost)
|
||||
if (!parsed) throw new Error(`cannot parse TypeScript config ${configPath}`)
|
||||
if (parsed.errors.length > 0) {
|
||||
throw new Error(parsed.errors.map(error => ts.flattenDiagnosticMessageText(error.messageText, '\n')).join('\n'))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@ import { globSync, readFileSync } from 'node:fs'
|
||||
import { dirname, relative, resolve } from 'node:path'
|
||||
import * as yaml from 'js-yaml'
|
||||
import ts from 'typescript'
|
||||
import { cordisConfigFiles } from './cordis-config-files.ts'
|
||||
|
||||
interface JsExpr {
|
||||
__jsExpr: string
|
||||
@@ -39,10 +40,7 @@ const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
|
||||
})
|
||||
const schema = yaml.JSON_SCHEMA.extend(jsExprType)
|
||||
|
||||
const files = globSync(['**/*cordis*.yml', '**/*cordis*.yaml'], {
|
||||
cwd: root,
|
||||
exclude: ['.claude/**', 'node_modules/**', 'vendor/**'],
|
||||
}).sort()
|
||||
const files = cordisConfigFiles(root)
|
||||
const errors: string[] = []
|
||||
const examplePluginReferences: PluginReference[] = []
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/client/ui-model-selector': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'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.' },
|
||||
'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.' },
|
||||
@@ -67,7 +68,6 @@ 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/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.' },
|
||||
@@ -91,6 +91,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/support/agent-loop-testkit': { kind: 'none', reason: 'The test helper mounts services but neither drives nor modifies model requests.' },
|
||||
'packages/support/invariants': { kind: 'none', reason: 'The observer validates requests but never rewrites their context.' },
|
||||
'packages/support/loader-smoke': { kind: 'none', reason: 'The test harness observes child-process streams without changing live requests.' },
|
||||
'packages/support/llm-mock-server': { kind: 'none', reason: 'The test server substitutes provider wire behavior without invoking a real model.' },
|
||||
'packages/support/llm-replay': { kind: 'none', reason: 'The keyless adapter invokes no provider model.' },
|
||||
'packages/tasks/tasks': { kind: 'indirect', reason: 'Producer and control-surface plugins own all model rendering over the task registry.' },
|
||||
'packages/examples/acp-demo': { kind: 'indirect', reason: 'The app bundle delegates request composition to dsh-agent-spine-demo and dsh-acp.' },
|
||||
|
||||
Reference in New Issue
Block a user