Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification

# Conflicts:
#	.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.i18n.yaml
#	examples/acp-agent/tests/snapshots/fs-glob-sampling/session.jsonl
#	packages/ui/tui/README.i18n.yaml
#	packages/ui/tui/src/components/dialogs.ts
#	packages/ui/tui/tests/tui.spec.ts
This commit is contained in:
_Kerman
2026-08-04 10:52:45 +08:00
74 changed files with 2492 additions and 1148 deletions

View File

@@ -1,7 +1,8 @@
import { readdirSync, readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { tmpdir } from 'node:os'
import { describe, expect, it } from 'vitest'
import { collectPythonDependencies, isPermissive, type Manifest, manifestPatterns, parsePyprojectRequirements, parseVendoredRows, render, tierExternalDeps } from './gen-third-party-notices.ts'
import { collectPythonDependencies, isPermissive, type Manifest, manifestPatterns, parsePyprojectRequirements, parseVendoredRows, render, tierExternalDeps, virtualManifest } from './gen-third-party-notices.ts'
const root = resolve(import.meta.dirname, '..')
@@ -63,6 +64,56 @@ describe('tierExternalDeps', () => {
})
})
describe('virtualManifest', () => {
it('resolves a manifest from an ordinary prefix-matching store directory', () => {
const root = mkdtempSync(join(tmpdir(), 'dsh-notices-prefix-'))
try {
const name = '@scope/pkg'
const version = '1.0.0'
const store = join(root, 'store')
const manifestDir = join(store, `${name.replace('/', '+')}@${version}`, 'node_modules', name)
mkdirSync(manifestDir, { recursive: true })
writeFileSync(join(manifestDir, 'package.json'), JSON.stringify({ name, version, license: 'MIT' }))
expect(virtualManifest(store, name)).toMatchObject({ name, version, license: 'MIT' })
} finally {
rmSync(root, { recursive: true, force: true })
}
})
it('falls back to a content scan when pnpm 11 truncates the store directory name', () => {
const root = mkdtempSync(join(tmpdir(), 'dsh-notices-truncated-'))
try {
const name = '@scope/pkg'
const version = '2.0.0'
const store = join(root, 'store')
// The truncated name no longer starts with `@scope+pkg@`, so only the
// whole-store content scan can find the package.
const manifestDir = join(store, '@scope+pkg_9f1c2d3e4a5b6c7d8e9f0a1b2c3d4e5f', 'node_modules', name)
mkdirSync(manifestDir, { recursive: true })
writeFileSync(join(manifestDir, 'package.json'), JSON.stringify({ name, version, license: 'Apache-2.0' }))
expect(virtualManifest(store, name)).toMatchObject({ name, version, license: 'Apache-2.0' })
} finally {
rmSync(root, { recursive: true, force: true })
}
})
it('returns undefined when neither the prefix nor the content scan finds the package', () => {
const root = mkdtempSync(join(tmpdir(), 'dsh-notices-miss-'))
try {
const store = join(root, 'store')
const other = join(store, 'other-pkg@1.0.0', 'node_modules', 'other-pkg')
mkdirSync(other, { recursive: true })
writeFileSync(join(other, 'package.json'), JSON.stringify({ name: 'other-pkg', version: '1.0.0' }))
expect(virtualManifest(store, '@scope/missing')).toBeUndefined()
} finally {
rmSync(root, { recursive: true, force: true })
}
})
})
describe('parseVendoredRows', () => {
it('reads the committed vendor manifest table', () => {
const rows = parseVendoredRows(readFileSync(resolve(root, 'vendor/README.md'), 'utf8'))

View File

@@ -141,15 +141,22 @@ function workspaceMembers(rel: string): string[] {
return declared.map(member => String(member))
}
/** Every workspace manifest, keyed by path, plus the set of workspace package names. */
/**
* Every workspace manifest, keyed by repository-relative path, plus the set of
* workspace package names. Paths are normalized to `/` at ingestion: Node's
* `fs.globSync` returns OS-native separators, and the area matching in
* `tierExternalDeps` compares `/`-suffixed prefixes, so Windows backslashes
* would silently push dev-area manifests into the runtime tier.
*/
function loadWorkspaceManifests(): { manifests: Map<string, Manifest>; names: Set<string> } {
const patterns = manifestPatterns(workspaceMembers('pnpm-workspace.yaml'), workspaceMembers('native/landlock-run/pnpm-workspace.yaml'))
const manifests = new Map<string, Manifest>()
const names = new Set<string>()
for (const pattern of patterns) {
for (const path of globSync(pattern, { cwd: root })) {
const manifest = readManifest(path)
manifests.set(path, manifest)
const normalized = path.replaceAll('\\', '/')
const manifest = readManifest(normalized)
manifests.set(normalized, manifest)
if (manifest.name !== undefined) names.add(manifest.name)
}
}
@@ -157,6 +164,35 @@ function loadWorkspaceManifests(): { manifests: Map<string, Manifest>; names: Se
return { manifests, names }
}
type VirtualManifest = Manifest & { license?: string; repository?: string | { url?: string }; homepage?: string }
/**
* Resolve one package's manifest inside a pnpm virtual store. The prefix scan
* matches ordinary `@scope+name@version` directory names; pnpm 11 truncates
* long names (a peer-suffixed name past the length limit becomes
* `<prefix>_<hash>`), so a content scan falls back over the whole store when
* the prefix misses.
*
* @param virtual - the `.pnpm` virtual store directory to scan.
* @param name - the external package name, exactly as `node_modules` spells it.
* @returns the parsed manifest, or `undefined` when neither the prefix match
* nor the content scan finds the package's `package.json`.
*/
export function virtualManifest(virtual: string, name: string): VirtualManifest | undefined {
const prefix = `${name.replace('/', '+')}@`
const entry = readdirSync(virtual).find(dir => dir.startsWith(prefix))
if (entry !== undefined) {
return JSON.parse(readFileSync(resolve(virtual, entry, 'node_modules', name, 'package.json'), 'utf8')) as VirtualManifest
}
for (const dir of readdirSync(virtual)) {
const candidate = resolve(virtual, dir, 'node_modules', name, 'package.json')
if (existsSync(candidate)) {
return JSON.parse(readFileSync(candidate, 'utf8')) as VirtualManifest
}
}
return undefined
}
/** License and repository URL for an installed external package, from the pnpm store. */
function installedMetadata(name: string): { license: string; repo: string } {
const override = OVERRIDES[name]
@@ -171,11 +207,8 @@ function installedMetadata(name: string): { license: string; repo: string } {
}
const virtual = resolve(root, store, '.pnpm')
if (!existsSync(virtual)) continue
const prefix = `${name.replace('/', '+')}@`
const entry = readdirSync(virtual).find(dir => dir.startsWith(prefix))
if (entry === undefined) continue
manifest = JSON.parse(readFileSync(resolve(virtual, entry, 'node_modules', name, 'package.json'), 'utf8')) as typeof manifest
break
manifest = virtualManifest(virtual, name)
if (manifest !== undefined) break
}
const license = override?.license ?? manifest?.license
const rawRepo = typeof manifest?.repository === 'string' ? manifest.repository : manifest?.repository?.url ?? manifest?.homepage

View File

@@ -18,8 +18,6 @@ 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'
import { BashExecutor } from '@deepseek-ai/dsh-bash'
import type { BashExecRequest, BashExecSpec, BashProcess, BashRunResult } from '@deepseek-ai/dsh-bash'
import LocalBashExecutor from '@deepseek-ai/dsh-bash-local'
import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
@@ -60,44 +58,6 @@ import * as ToolWorkflow from '@deepseek-ai/dsh-tool-workflow'
const root = resolve(import.meta.dirname, '..')
const OUT = 'docs/tool-catalog.md'
const CATALOG_RG_PROBE_COMMAND = 'command -v rg >/dev/null 2>&1'
/**
* Minimal bash service for harvesting `dsh-tool-fs-search` schemas. The search
* plugin now probes `rg` at registration time, but the generated catalog must
* remain independent of the host PATH and never execute a real search.
*/
class CatalogSearchBashExecutor extends BashExecutor {
override resolve(request: BashExecRequest): BashExecSpec {
return {
command: request.command,
workdir: request.workdir ?? root,
timeoutMs: request.timeoutMs ?? 60_000,
stdoutMaxBytes: request.stdoutMaxBytes ?? 64_000,
signal: request.signal,
sandboxPolicy: request.sandboxPolicy,
}
}
override run(spec: BashExecSpec): Promise<BashRunResult> {
if (spec.command !== CATALOG_RG_PROBE_COMMAND) {
throw new Error(`gen-tool-catalog: unexpected search bash command during schema harvest: ${spec.command}`)
}
return Promise.resolve({
exitCode: 0,
signal: null,
timedOut: false,
aborted: false,
timeoutMs: spec.timeoutMs,
stdout: { text: '', truncated: false },
stderr: { text: '', truncated: false },
})
}
override start(): BashProcess {
throw new Error('gen-tool-catalog: search schema harvest must not start background processes')
}
}
/**
* Register the descriptor needed to mount schema-producing consumers. Declares
@@ -297,19 +257,19 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-fs-search',
dir: 'tool-fs-search',
source: 'packages/fs/tool-fs-search/src/index.ts',
requires: ['ctx.tools', 'ctx.bash', 'ctx.systemPrompt'],
requires: ['ctx.tools', 'ctx.subprocess', 'ctx.systemPrompt'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
// The tools inject `bash` (search executes fixed `rg` commands through
// the executor seam, not ctx.fs). Use a catalog-only executor so the
// registration-time `rg` probe stays deterministic and the generator
// never depends on the host PATH. `ctx.spillStore` is optional (read via
// ctx.get) and does not affect the schemas, so no spill backend is mounted.
await ctx.plugin(CatalogSearchBashExecutor)
// The tools inject `subprocess` (search spawns the packaged ripgrep
// binary through the seam, not ctx.fs); registration itself never
// spawns, so the real local service is inert here. `ctx.spillStore` is
// optional (read via ctx.get) and does not affect the schemas, so no
// spill backend is mounted.
await ctx.plugin(LocalSubprocessService)
await ctx.plugin(ToolFsSearch, { sampleOverCapGlobResults: true })
},
note:
'glob and grep are conditional bash-backed discovery tools: they register only when ctx.bash can find `rg`, then run fixed ripgrep commands through ctx.bash as ordinary foreground calls (never background tasks). The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments.',
'glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (`@vscode/ripgrep`) through ctx.subprocess as ordinary foreground calls (never background tasks) — no host `rg` install and no shell layer. The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments.',
},
{
pkg: '@deepseek-ai/dsh-tool-pty',