Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ diff --git a/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/client.js
if (path === void 0) return void 0;
return {
open: () => {
@@ -218,6 +218,21 @@
@@ -218,6 +218,28 @@
title: path
};
} };
Expand All @@ -20,12 +20,19 @@ diff --git a/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/client.js
+ * in the current turn. Keep bare identifiers inert: without a slash, an
+ * absolute-path prefix, or a conventional filename suffix, inline code is
+ * much more likely to be a command, package, or symbol than a local path.
+ *
+ * A leading `@` plus a single slash is a scoped package name, and a bare
+ * `local@domain.tld` is an email address; both are far more common in
+ * assistant prose than files with those exact shapes, and neither is
+ * openable. An explicit `./@scope/pkg` still resolves as a path above.
+ */
+ function localPathReference(value) {
+ const candidate = value.trim();
+ if (candidate === "" || /[\r\n]/.test(candidate) || /^(?:https?|data|javascript):/i.test(candidate)) return void 0;
+ const path = candidate.replace(/(?:#L\d+(?:C\d+)?|:\d+(?::\d+)?)$/, "");
+ if (/^(?:\/|~[\\/]|\.{1,2}[\\/]|[A-Za-z]:[\\/]|\\\\)/.test(path)) return path;
+ if (/^@[^\\/@\s]+\/[^\\/@\s]+$/.test(path)) return void 0;
+ if (/^[^@\s\\/]+@[^@\s\\/]+\.[A-Za-z]{2,}$/.test(path)) return void 0;
+ if (/[\\/]/.test(path) || /[\\/]$/.test(path)) return path;
+ if (/^[^\\/]+\.[A-Za-z0-9][A-Za-z0-9._-]{0,15}$/.test(path)) return path;
+ return void 0;
Expand Down
92 changes: 91 additions & 1 deletion test/local-path-links.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,35 @@
import { readFile } from 'node:fs/promises'
import { describe, expect, it } from 'vitest'
import { patchPath, projectRoot } from './patch-path'
import { patchPath } from './patch-path'

/**
* The patch is the source of truth for this helper: `node_modules` may hold a
* different Harness build than the one the patch targets, so read the added
* lines straight out of the patch and evaluate them.
*/
async function loadLocalPathReference(): Promise<
(value: string) => string | undefined
> {
const patch = await readFile(
patchPath('@deepseek-ai/dsh-client-ui-deliverables'),
'utf8'
)
const added = patch
.split('\n')
.filter((line) => line.startsWith('+') && !line.startsWith('+++'))
.map((line) => line.slice(1))
.join('\n')
// The function's closing brace is a context line, not an added one, so the
// extracted body stops at the last `return` and is closed here.
const source = added.match(
/function localPathReference\(value\) \{[\s\S]*?\n\t\t\treturn void 0;/
)?.[0]

expect(source).toBeDefined()
return new Function(
`${source}\n}; return localPathReference`
)() as (value: string) => string | undefined
}

describe('assistant local path links', () => {
it('links Codex-style path references even when they are not turn deliverables', async () => {
Expand All @@ -15,4 +44,65 @@ describe('assistant local path links', () => {
expect(patch).toContain('[A-Za-z]:[\\\\/]')
expect(patch).toContain('owner.openFile')
})

it('resolves real local paths', async () => {
const localPathReference = await loadLocalPathReference()

for (const value of [
'src/main.ts',
'./scripts/build.mjs',
'../sibling/file.txt',
'C:\\Users\\me\\file.txt',
'/etc/hosts',
'~/notes.md',
'package.json',
'vitest.config.ts',
'docs/',
]) {
expect(localPathReference(value), value).toBe(value)
}
})

it('keeps scoped package names and email addresses inert', async () => {
const localPathReference = await loadLocalPathReference()

for (const value of [
'@deepseek-ai/cordis',
'@deepseek-ai/dsh-client-ui-deliverables',
'@foo/bar',
'@plugin/name',
'user@example.com',
'first.last@sub.example.co',
]) {
expect(localPathReference(value), value).toBeUndefined()
}
})

it('still resolves paths that merely contain an @ segment', async () => {
const localPathReference = await loadLocalPathReference()

for (const value of [
'./@scope/pkg',
'/tmp/@scope/pkg/index.js',
'patches/@deepseek-ai+dsh-client-ui-deliverables+0.1.2-rc.1.patch',
'node_modules/@foo/bar/lib/client.js',
]) {
expect(localPathReference(value), value).toBe(value)
}
})

it('keeps bare identifiers and commands inert', async () => {
const localPathReference = await loadLocalPathReference()

for (const value of ['npm install', 'someFunction', '', ' ']) {
expect(localPathReference(value), value).toBeUndefined()
}
})

it('strips line and column suffixes from resolved paths', async () => {
const localPathReference = await loadLocalPathReference()

expect(localPathReference('src/main.ts#L42')).toBe('src/main.ts')
expect(localPathReference('src/main.ts:42:7')).toBe('src/main.ts')
})
})