diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml
index ef4931f765..e5b36bf91c 100644
--- a/docs/config-catalog.i18n.yaml
+++ b/docs/config-catalog.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/config-catalog.md
-config-catalog.md: 82f6d26c79d32c6952f3bc11c96fa1c2ddceecdc
+config-catalog.md: 2e4aad7532b061e8328f25a53c2c3b0c4bb4dfa0
config-catalog.zh.md: 958d3115447db37de248bbf30b0744308ff8dbb8
diff --git a/docs/config-catalog.md b/docs/config-catalog.md
index 82f6d26c79..2e4aad7532 100644
--- a/docs/config-catalog.md
+++ b/docs/config-catalog.md
@@ -2370,7 +2370,7 @@ export interface Config {
}
```
-Source: [`packages/shell/tool-bash-persistent/src/index.ts:405`](../packages/shell/tool-bash-persistent/src/index.ts)
+Source: [`packages/shell/tool-bash-persistent/src/index.ts:406`](../packages/shell/tool-bash-persistent/src/index.ts)
diff --git a/docs/subsystems/terminal.i18n.yaml b/docs/subsystems/terminal.i18n.yaml
index 859104d415..96f05cc50a 100644
--- a/docs/subsystems/terminal.i18n.yaml
+++ b/docs/subsystems/terminal.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/terminal.md
-terminal.md: eedf6157b256e83d3b57e07bf738429773b87574
-terminal.zh.md: 3cc2c281e8c2090bb7e92808f932be689fd0b710
+terminal.md: c7031143117a514f8579a22fed07a1461babb15e
+terminal.zh.md: 7c469c028e8b3da7c9d5012f9f49c1ae65a9bd03
diff --git a/docs/subsystems/terminal.md b/docs/subsystems/terminal.md
index eedf6157b2..c703114311 100644
--- a/docs/subsystems/terminal.md
+++ b/docs/subsystems/terminal.md
@@ -180,5 +180,5 @@ list(owner: Agent): TerminalSessionSnapshot[]
Types: [Agent](core.md)
-Source: [`packages/terminal/terminal/src/index.ts:105`](../../packages/terminal/terminal/src/index.ts)
+Source: [`packages/terminal/terminal/src/index.ts:108`](../../packages/terminal/terminal/src/index.ts)
diff --git a/docs/subsystems/terminal.zh.md b/docs/subsystems/terminal.zh.md
index 3cc2c281e8..7c469c028e 100644
--- a/docs/subsystems/terminal.zh.md
+++ b/docs/subsystems/terminal.zh.md
@@ -180,5 +180,5 @@ list(owner: Agent): TerminalSessionSnapshot[]
Types: [Agent](core.md)
-Source: [`packages/terminal/terminal/src/index.ts:105`](../../packages/terminal/terminal/src/index.ts)
+Source: [`packages/terminal/terminal/src/index.ts:108`](../../packages/terminal/terminal/src/index.ts)
diff --git a/packages/fs/tool-fs-search/tests/rg-sidecar.spec.ts b/packages/fs/tool-fs-search/tests/rg-sidecar.spec.ts
new file mode 100644
index 0000000000..49cca6bfa8
--- /dev/null
+++ b/packages/fs/tool-fs-search/tests/rg-sidecar.spec.ts
@@ -0,0 +1,25 @@
+import { describe, expect, it, vi } from 'vitest'
+
+const existsSync = vi.hoisted(() => vi.fn(() => true))
+
+vi.mock('node:fs', async (importOriginal) => {
+ const actual = await importOriginal()
+ return { ...actual, existsSync }
+})
+
+vi.mock('@vscode/ripgrep', () => new Proxy({}, {
+ get() {
+ throw new Error('the platform package must not load when the executable sidecar exists')
+ },
+}))
+
+import { resolveRgPath } from '@deepseek-ai/dsh-tool-fs-search'
+
+describe('single-executable ripgrep resolution', () => {
+ it('uses the native sidecar beside the current executable', async () => {
+ const sidecar = `${process.execPath}-rg`
+
+ await expect(resolveRgPath()).resolves.toBe(sidecar)
+ expect(existsSync).toHaveBeenCalledWith(sidecar)
+ })
+})
diff --git a/packages/subprocess/subprocess-local/tests/process-inspector.spec.ts b/packages/subprocess/subprocess-local/tests/process-inspector.spec.ts
index 84cae7564a..2402681461 100644
--- a/packages/subprocess/subprocess-local/tests/process-inspector.spec.ts
+++ b/packages/subprocess/subprocess-local/tests/process-inspector.spec.ts
@@ -140,6 +140,32 @@ describe('Linux process inspector', () => {
])
})
+ it('contains cycles in the procfs children index', () => {
+ const fake = fakeInternals()
+ fake.files.set('/proc/10/stat', stat(10, 10, 10, 10, '500'))
+ fake.files.set('/proc/10/task/10/children', '11')
+ fake.files.set('/proc/11/stat', stat(11, 10, 10, 10, '501', 10))
+ fake.files.set('/proc/11/task/11/children', '10')
+
+ expect(createProcessInspector('linux', 'x64', fake.internals).processTree(10)).toEqual([
+ { pid: 11, started: '501' },
+ { pid: 10, started: '500' },
+ ])
+ })
+
+ it('falls back to the PID namespace when a descendant children index is unreadable', () => {
+ const fake = fakeInternals()
+ fake.dirs.set('/proc', ['10', '11', '12'])
+ fake.files.set('/proc/10/stat', stat(10, 10, 10, 10, '500'))
+ fake.files.set('/proc/10/task/10/children', '12 invalid 11')
+ fake.files.set('/proc/11/stat', stat(11, 10, 10, 10, '501', 10))
+
+ expect(createProcessInspector('linux', 'x64', fake.internals).processTree(10)).toEqual([
+ { pid: 11, started: '501' },
+ { pid: 10, started: '500' },
+ ])
+ })
+
it('keeps readiness inspection local when procfs has no children index', () => {
const fake = fakeInternals()
fake.files.set('/proc/10/stat', stat(10, 10, 10, 10, '500'))
@@ -147,11 +173,19 @@ describe('Linux process inspector', () => {
expect(inspector.processTree(10, false)).toEqual([{ pid: 10, started: '500' }])
expect(inspector.isStdinWaiting(10, false)).toBe(false)
+
+ const readFile = fake.internals.readFile.bind(fake.internals)
+ let statReads = 0
+ fake.internals.readFile = (path) => {
+ if (path === '/proc/10/stat' && statReads++ > 0) throw new Error('process exited')
+ return readFile(path)
+ }
+ expect(inspector.processTree(10, false)).toEqual([])
})
it('detects read, select, poll, and epoll waits across non-leader threads', () => {
const fake = fakeInternals()
- fake.dirs.set('/proc', ['100', '101'])
+ fake.dirs.set('/proc', ['77', '100', '101'])
fake.files.set('/proc/100/stat', stat(100, 77, 100, 77, '1'))
fake.files.set('/proc/101/stat', stat(101, 77, 100, 77, '2'))
fake.dirs.set('/proc/100/task', ['100'])
diff --git a/python/sdk/tests/test_runtime_resolution.py b/python/sdk/tests/test_runtime_resolution.py
index 778203f4d1..14e90f3283 100644
--- a/python/sdk/tests/test_runtime_resolution.py
+++ b/python/sdk/tests/test_runtime_resolution.py
@@ -51,7 +51,10 @@ def test_runtime_requires_spawn_helper_only_on_macos(
runtime_dir.mkdir()
linux = runtime_dir / "dsh-jsonrpc-agent-pkg-linux-x64"
linux.touch()
- (runtime_dir / "dsh-jsonrpc-agent-pkg-macos-arm64").touch()
+ Path(f"{linux}-rg").touch()
+ macos = runtime_dir / "dsh-jsonrpc-agent-pkg-macos-arm64"
+ macos.touch()
+ Path(f"{macos}-rg").touch()
monkeypatch.setattr(runtime, "bundled_package_dir", lambda: tmp_path)
monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "macos-arm64")
@@ -59,3 +62,16 @@ def test_runtime_requires_spawn_helper_only_on_macos(
runtime.bundled_runtime_path()
monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "linux-x64")
assert runtime.bundled_runtime_path() == linux
+
+
+def test_runtime_requires_ripgrep_sidecar(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ runtime_dir = tmp_path / "runtime"
+ runtime_dir.mkdir()
+ (runtime_dir / "dsh-jsonrpc-agent-pkg-linux-x64").touch()
+ monkeypatch.setattr(runtime, "bundled_package_dir", lambda: tmp_path)
+ monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "linux-x64")
+
+ with pytest.raises(FileNotFoundError, match="ripgrep sidecar"):
+ runtime.bundled_runtime_path()
diff --git a/scripts/cordis-yaml.ts b/scripts/cordis-yaml.ts
new file mode 100644
index 0000000000..60cd492787
--- /dev/null
+++ b/scripts/cordis-yaml.ts
@@ -0,0 +1,26 @@
+import * as yaml from 'js-yaml'
+
+export interface JsExpr {
+ __jsExpr: string
+}
+
+const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
+ kind: 'scalar',
+ resolve: data => typeof data === 'string',
+ construct: (data: unknown): JsExpr => {
+ if (typeof data !== 'string') throw new TypeError('!!js requires a scalar string')
+ return { __jsExpr: data }
+ },
+})
+const schema = yaml.JSON_SCHEMA.extend(jsExprType)
+
+/** Parse a Cordis config while preserving Loader `!!js` expressions as data. */
+export function loadCordisYaml(source: string): unknown {
+ return yaml.load(source, { schema })
+}
+
+export function isJsExpr(value: unknown): value is JsExpr {
+ return typeof value === 'object'
+ && value !== null
+ && typeof (value as Record).__jsExpr === 'string'
+}
diff --git a/scripts/verify-cordis-config.ts b/scripts/verify-cordis-config.ts
index f2e0aef46c..9a94a281e8 100644
--- a/scripts/verify-cordis-config.ts
+++ b/scripts/verify-cordis-config.ts
@@ -12,13 +12,9 @@
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
-}
+import { isJsExpr, loadCordisYaml } from './cordis-yaml.ts'
interface PackageManifest {
name?: string
@@ -56,16 +52,6 @@ const CHOOSER_BACKEND_PACKAGES = [
'@deepseek-ai/dsh-client-ui-directory-picker-browse',
'@deepseek-ai/dsh-client-ui-directory-picker-native',
]
-const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
- kind: 'scalar',
- resolve: data => typeof data === 'string',
- construct: (data: unknown): JsExpr => {
- if (typeof data !== 'string') throw new TypeError('!!js requires a scalar string')
- return { __jsExpr: data }
- },
-})
-const schema = yaml.JSON_SCHEMA.extend(jsExprType)
-
const errors: string[] = []
const pluginReferences: PluginReference[] = []
@@ -73,7 +59,7 @@ if (import.meta.main) {
const files = cordisConfigFiles(root)
for (const file of files) {
- const document: unknown = yaml.load(readFileSync(resolve(root, file), 'utf8'), { schema })
+ const document = loadCordisYaml(readFileSync(resolve(root, file), 'utf8'))
if (!isUnknownArray(document)) {
errors.push(`${file}: root must be a Loader entry array`)
continue
@@ -172,7 +158,7 @@ function validatePresetPlaneSeparation(): string[] {
/** Every entry of one config file, or an empty list when it is not an entry array. */
function loadEntries(file: string): unknown[] {
- const document: unknown = yaml.load(readFileSync(resolve(root, file), 'utf8'), { schema })
+ const document = loadCordisYaml(readFileSync(resolve(root, file), 'utf8'))
return isUnknownArray(document) ? document : []
}
@@ -462,7 +448,6 @@ export function metadataExpressionErrors(entry: Record, path: s
function disabledExpressionProblem(expression: string): string | undefined {
try {
// Compilation only — the constructor never executes the body.
- // oxlint-disable-next-line typescript/no-implied-eval
new Function(`return (${expression})`)
return undefined
} catch (error) {
@@ -484,10 +469,6 @@ function collectExpressionPaths(value: unknown, path: string, output: string[]):
for (const [key, child] of Object.entries(value)) collectExpressionPaths(child, `${path}.${key}`, output)
}
-function isJsExpr(value: unknown): value is JsExpr {
- return isRecord(value) && typeof value.__jsExpr === 'string'
-}
-
function isRecord(value: unknown): value is Record {
return value !== null && typeof value === 'object'
}
diff --git a/scripts/verify-runtime-closure.ts b/scripts/verify-runtime-closure.ts
index c0015fe11a..1287542dc2 100644
--- a/scripts/verify-runtime-closure.ts
+++ b/scripts/verify-runtime-closure.ts
@@ -8,11 +8,7 @@ import { globSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { basename, dirname, resolve } from 'node:path'
import { parseArgs } from 'node:util'
-import * as yaml from 'js-yaml'
-
-interface JsExpr {
- __jsExpr: string
-}
+import { loadCordisYaml } from './cordis-yaml.ts'
interface PackageManifest {
name?: string
@@ -34,16 +30,6 @@ interface RuntimePlatform {
type RuntimePlatformManifest = Record
-const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
- kind: 'scalar',
- resolve: data => typeof data === 'string',
- construct: (data: unknown): JsExpr => {
- if (typeof data !== 'string') throw new TypeError('!!js requires a scalar string')
- return { __jsExpr: data }
- },
-})
-const schema = yaml.JSON_SCHEMA.extend(jsExprType)
-
const root = resolve(import.meta.dirname, '..')
const { values } = parseArgs({
args: process.argv.slice(2),
@@ -107,7 +93,7 @@ async function missingPresetPlugins(
const failures: string[] = []
const presetPaths = globSync('apps/cli/config/agent-presets/*/agent.cordis.yml', { cwd: root }).sort()
for (const presetPath of presetPaths) {
- const document: unknown = yaml.load(await readFile(resolve(root, presetPath), 'utf8'), { schema })
+ const document = loadCordisYaml(await readFile(resolve(root, presetPath), 'utf8'))
if (!Array.isArray(document)) {
failures.push(`${presetPath}: preset root must be a Loader entry array`)
continue