mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
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:
@@ -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'))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user