Merge remote-tracking branch 'origin/xtr/session-format-migration' into xtr/message-tool-call-id

# Conflicts:
#	.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-20-branded-ids.md
#	.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md
#	packages/fs/tool-fs/tests/read-image.spec.ts
#	packages/llm/llm-deepseek/tests/adapter.e2e.ts
#	packages/llm/llm-deepseek/tests/serialize.spec.ts
#	packages/llm/llm-pi-ai/src/context.ts
#	packages/llm/llm-pi-ai/tests/context.spec.ts
#	packages/llm/llm-pi-ai/tests/convert.spec.ts
#	packages/llm/llm/src/message.ts
#	packages/llm/llm/tests/content.spec.ts
This commit is contained in:
_Kerman
2026-08-22 16:03:48 +08:00
1248 changed files with 29363 additions and 5465 deletions
+13 -4
View File
@@ -58,9 +58,10 @@ const releaseMemberDirectory = /^(?:packages\/(?!experimental\/)[^/]+\/[^/]+|app
const localArtifactDirs = new Set(['node_modules'])
const appPackageFiles: Readonly<Record<string, readonly string[]>> = {
'@deepseek-ai/dsh': ['lib/*.js', 'config'],
// The Web build emits sourcemaps for browser debugging; publishing them is
// what the payload policy forbids, so the bundle ships without them.
'@deepseek-ai/dsh-web-frontend': ['dist', '!dist/**/*.map'],
// Sourcemaps stay out by payload policy; the worker-preview surface
// (dist/preview.html and dist/preview/) backs private experimental
// packages and is not published.
'@deepseek-ai/dsh-web-frontend': ['dist', '!dist/**/*.map', '!dist/preview.html', '!dist/preview'],
}
/** The subset of package.json fields this constraint check cares about. */
@@ -161,6 +162,10 @@ const packageFileExtras: Readonly<Record<string, readonly string[]>> = {
// SQLite loads every statement from immutable package resources at runtime.
'@deepseek-ai/dsh-session-persistence-sqlite': ['resources/sql/**/*.sql'],
'@deepseek-ai/dsh-skill-badge': ['assets'],
// tsdown shares the repository/pack code between the lib entry and the bin
// through a hashed chunk. The committed bin.js is the link target pnpm can
// resolve at install time, before the build produces lib/bin.js.
'@deepseek-ai/dsh-experimental-webworker-packer': ['bin.js', 'lib/repository-*.js'],
'@deepseek-ai/dsh-subprocess-local': ['scripts/ensure-spawn-helper.mjs'],
}
@@ -181,7 +186,11 @@ function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] {
// bundle; the package-invariant gate validates the companion itself.
'lib/invariant.js',
...manifest.bin ? ['lib/bin.js'] : [],
...manifest.exports?.['./worker'] ? ['lib/worker.cjs'] : [],
// Worker-thread packages ship a CJS worker entry; the browser worker
// bundle is an ES module a page loads with `new Worker(type: 'module')`.
// Keyed on the artifact path, like ./client below.
...exportDefault(manifest, './worker') === './lib/worker.cjs' ? ['lib/worker.cjs'] : [],
...exportDefault(manifest, './worker') === './lib/worker.js' ? ['lib/worker.js'] : [],
// UI plugin packages ship their browser bundle beside the node lib
// (single-artifact ruling: dist/ retired, ./client resolves lib/client.js).
// Keyed on the artifact path, not the subpath name: apiproxy's ./client is
+1 -2
View File
@@ -42,7 +42,7 @@ const MAX_DECL_CHARS = 1200
/**
* Line budget for ONE slot's expanded report. The whole point of narrowing to a
* single slot is to spend less context, so a report a model cannot finish
* reading is a defect rather than a detail. Today's widest slot renders 60
* reading is a defect rather than a detail. The widest measured slot renders 60
* lines, so this leaves room to document a slot properly while catching the two
* ways a report runs away: an owner share that hands down a subsystem instead of
* a share, and prose that grew into a manual.
@@ -552,7 +552,6 @@ export function main(): void {
console.log(`gen-client-catalog: wrote ${OUT}.`)
}
// Run only when invoked as a script, not when imported by a test.
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
main()
}
-1
View File
@@ -874,7 +874,6 @@ function main(): void {
console.log(`gen-config-catalog: wrote ${OUT}.`)
}
// Run only when invoked as a script, not when imported by a test.
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
main()
}
+2 -1
View File
@@ -293,6 +293,8 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
ApprovalService: 'approval.md',
EncodedImageAttachment: 'attachment.md',
ImageAttachmentRef: 'attachment.md',
ImageRequestPolicy: 'attachment.md',
RequestImageAttachment: 'attachment.md',
SaveImageAttachment: 'attachment.md',
StoredImageAttachment: 'attachment.md',
ShellExecRequest: 'shell.md',
@@ -1006,7 +1008,6 @@ export function main(): void {
console.log(`gen-cordis-catalog: ${outputs.length} artifact(s) computed, ${changedPages} written, ${recorded} pair record(s) refreshed.`)
}
// Run only when invoked as a script, not when imported by a test.
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
main()
}
-1
View File
@@ -466,7 +466,6 @@ function main(): void {
}
}
// Run only when invoked as a script, not when imported by a test.
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
main()
}
-1
View File
@@ -768,7 +768,6 @@ function main(): void {
console.log(`gen-third-party-notices: wrote ${OUT}.`)
}
// Run only when invoked as a script, not when imported by a test.
if (process.argv[1] !== undefined && import.meta.filename === resolve(process.argv[1])) {
main()
}
+6 -7
View File
@@ -314,18 +314,18 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-fs',
dir: 'tool-fs',
source: 'packages/fs/tool-fs/src/index.ts',
requires: ['ctx.tools', 'ctx.fs', 'ctx.systemPrompt', 'ctx.attachments (read_image registration)', 'ctx.llm + an image-capable route (read_image execution)'],
requires: ['ctx.tools', 'ctx.fs', 'ctx.systemPrompt', 'ctx.attachments (image-tool registration)', 'ctx.llm + an image-capable route (image-tool execution)'],
writes: ['tool/call', 'fs/write-intent or fs/edit-intent for mutations', 'fs/observed after read presence/absence or successful file operation', 'durable attachment (read_image)', 'tool/result'],
async mount(ctx) {
// The tool needs `fs`; the bare provider is sufficient because policy
// changes behavior, not schema shape. The catalog seam marker opts into
// the attachments-conditional read_image schema without attachment I/O.
// the attachments-conditional image schema without attachment I/O.
await ctx.plugin(LocalFileSystem)
await ctx.plugin(CatalogAttachmentStore)
await ctx.plugin(ToolFs)
},
note:
'The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-observation-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. `read_image` is not registered without `ctx.attachments`; its schema is route-independent, and execution refuses unless the exact routed model declares image input.',
'The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-observation-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. The image tool is not registered without `ctx.attachments`; its schema is route-independent, and execution refuses unless the exact routed model declares image input.',
},
{
pkg: '@deepseek-ai/dsh-tool-fs-search',
@@ -651,9 +651,9 @@ export function assertManifestComplete(packages: ToolPackage[] = TOOL_PACKAGES,
* A tool package that boots without registering anything is a broken boot, not
* an empty catalog section. The usual cause is an `inject` the entry's `mount`
* does not satisfy: cordis leaves the plugin PENDING, every step here still
* succeeds, and the generator writes a catalog missing that package's tools
* with the freshness gate green on it, because the omission is now what the
* generator produces. {@link assertManifestComplete} cannot see this: the
* succeeds, and the generator writes a catalog missing that package's tools.
* The freshness gate stays green because regeneration reproduces the omission.
* {@link assertManifestComplete} cannot see this: the
* package IS listed, it just contributed nothing.
* @param entry - the manifest entry that was booted.
* @param harvested - how many schemas its boot registered.
@@ -808,7 +808,6 @@ async function main(): Promise<void> {
console.log(`gen-tool-catalog: wrote ${OUT}.`)
}
// Run only when invoked as a script, not when imported by a test.
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
await main()
}
+7 -7
View File
@@ -18,19 +18,19 @@ interface Profile {
// snapshot; they do not re-evaluate that deleted baseline or track its preset.
const profiles = {
source: {
count: 88,
count: 89,
indexes: [0, 1, 4, 5],
sha256: 'da1dfd77cb6eb66be93d8d3820f9b9b68b7aa391c24680f8851c0910298f9e3b',
sha256: '3e8b8455827254d6da59afb803d30f062b9fe1264b4df75ce4c8cd6e742b93dd',
},
example: {
count: 87,
count: 88,
indexes: [0, 1, 2, 4, 5],
sha256: '6a2606053bc1ec1de3b02611de88ea51d201dac13a1f193e4934d33c08b95f08',
sha256: 'e2b944c028ef1971302d47b2eb0e95c05e740623c7cbaebb4b1312dbf73bf156',
},
test: {
count: 83,
count: 84,
indexes: [0, 3, 4, 5],
sha256: '7995e14926a36c40bd65c474637735222a95fb030395681685f03060e50a7b78',
sha256: '7800bf57f0d689a5623e4ad60315b10173158fb9cf988d3f005cb115f2245691',
},
} as const satisfies Record<string, Profile>
@@ -85,7 +85,7 @@ describe('Oxlint repository rule fingerprint', () => {
const overrides: readonly unknown[] = parsed.overrides
it('pins every override field', () => {
expect(overrides).toHaveLength(8)
expect(overrides).toHaveLength(9)
})
it.each(Object.entries(profiles))('pins the %s rule profile', (_name, profile) => {
+20 -3
View File
@@ -177,6 +177,22 @@ function relativeImports(file: string, sourceText: string): RelativeImport[] {
return imports
}
/**
* publint reads the CommonJS interop preamble inside the prebuilt browser
* bundles and reports CJS-written-as-ESM. Node never resolves those files:
* `lib/client.js` is evaluated by the page module system as a classic script,
* and `lib/worker.js` by `new Worker(url, { type: 'module' })` — both outside
* the Node resolution publint models. Exactly that verdict on exactly those
* files is suppressed; every other publint error stays fatal.
*/
function isBrowserBundleFormatFalsePositive(message: Message): boolean {
if (message.code !== 'FILE_INVALID_FORMAT') return false
const filePath = (message.args as { actualFilePath?: string }).actualFilePath ?? ''
const exportKey = Array.isArray(message.path) ? message.path.join('/') : ''
return /(^|\/)lib\/(client|worker)\.js$/.test(filePath)
|| /(^|\/)\.\/(client|worker)$/.test(exportKey)
}
async function runPublint(target: PackageTarget): Promise<PublintResult> {
try {
const files = publicationFiles(target)
@@ -186,9 +202,10 @@ async function runPublint(target: PackageTarget): Promise<PublintResult> {
pack: { files },
})
const manifest = result.pkg as Record<string, unknown>
return result.messages.some(message => message.type === 'error') || closureViolations.length > 0
? { path: target.path, status: 'failed', messages: result.messages, closureViolations, manifest }
: { path: target.path, status: 'passed', messages: result.messages, closureViolations, manifest }
const messages = result.messages.filter(message => !isBrowserBundleFormatFalsePositive(message))
return messages.some(message => message.type === 'error') || closureViolations.length > 0
? { path: target.path, status: 'failed', messages, closureViolations, manifest }
: { path: target.path, status: 'passed', messages, closureViolations, manifest }
} catch (error: unknown) {
return {
path: target.path,
-4
View File
@@ -40,13 +40,9 @@ const ROOT_MANIFEST = 'package.json'
/** One manifest the bump rewrites, and the tag its new version will carry. */
interface PlannedVersion {
/** Repository-relative manifest path. */
readonly manifestPath: string
/** Label for the log line. */
readonly label: string
/** The version the manifest currently carries. */
readonly from: string
/** The version to write. */
readonly to: string
/** The tag this version publishes from, or undefined for a non-published manifest. */
readonly tag: string | undefined
+2 -10
View File
@@ -38,7 +38,6 @@ const WORKSPACE_ROOT_PACKAGE = '@deepseek-ai/dsh-root'
/** One peer declaration the publish order leaves unordered. */
interface DroppedPeerEdge {
/** Package declaring the peer. */
readonly consumer: string
/** The declared peer, which publishes after `consumer` or alongside it in a cycle. */
readonly peer: string
@@ -52,7 +51,6 @@ interface DroppedPeerEdge {
* log is the only one who can judge whether a newly dropped edge is expected.
*/
export interface PublishPlan {
/** Members in publish order. */
readonly order: readonly ReleaseMember[]
/** Peer declarations left unordered, in the order the traversal reached them. */
readonly droppedPeerEdges: readonly DroppedPeerEdge[]
@@ -60,13 +58,9 @@ export interface PublishPlan {
/** One publishable package of a release family. */
export interface ReleaseMember {
/** Repository-relative package directory, for example `packages/core/session`. */
readonly directory: string
/** Package name from its manifest. */
readonly name: string
/** Package version from its manifest. */
readonly version: string
/** The parsed manifest, for payload policy and publication checks. */
readonly manifest: Readonly<Record<string, unknown>>
}
@@ -98,18 +92,16 @@ function requireString(manifest: Record<string, unknown>, field: string, context
/** The executable a family's installed artifacts are driven through. */
export interface InstalledEntry {
/** Package that carries the executable. */
readonly packageName: string
/** Path to the executable inside that package. */
readonly binPath: string
}
/** A release sequence: its members, its version baseline, and its tag naming. */
export abstract class ReleaseFamily {
/** Workflow-facing identifier, also the `--family` argument. */
/** Workflow-facing `--family` identifier. */
abstract readonly id: string
/** Glob patterns, relative to the repository root, that select this family's manifests. */
/** Repository-relative glob patterns selecting this family's manifests. */
abstract readonly patterns: readonly string[]
/** Git tag prefix this family publishes from. */
+3 -24
View File
@@ -9,9 +9,7 @@ import { fileURLToPath } from 'node:url'
/** Where and with what environment a release step runs a command. */
export interface RunOptions {
/** Working directory; defaults to the current one. */
readonly cwd?: string
/** Child environment; defaults to this process's. */
readonly env?: NodeJS.ProcessEnv
}
@@ -19,9 +17,7 @@ export interface RunOptions {
export interface CommandResult {
/** Exit status, or null when a signal ended the process. */
readonly status: number | null
/** Captured standard output. */
readonly stdout: string
/** Captured standard error. */
readonly stderr: string
}
@@ -39,19 +35,8 @@ export function attempt(command: string, args: readonly string[], options: RunOp
}
/**
* Run a command, capture its output, and echo it once the command exits.
*
* A step that both shows what a command said and classifies its own failure
* needs both halves: the output has to reach the workflow log, and the caller has
* to read it to decide whether a failure is worth retrying.
*
* This is not live progress. `spawnSync` returns only after the child exits, so
* nothing appears while the command runs, and the two streams are echoed one
* after the other — all of stdout, then all of stderr — which loses their
* interleaving. For an npm publish that matters in one visible way: `npm notice`
* lines go to stderr while the `+ name@version` confirmation goes to stdout, so
* the log shows the confirmation first. Live progress would need an
* asynchronous spawn with data listeners.
* Run a command, then echo and return its captured output. Output is buffered
* until exit and stdout precedes stderr.
* @param command - executable name.
* @param args - command arguments.
* @param options - working directory and environment.
@@ -62,8 +47,6 @@ export function attemptEchoed(command: string, args: readonly string[], options:
cwd: options.cwd,
env: options.env,
encoding: 'utf8',
// 'inherit' would leave nothing to capture, so the streams are piped and
// echoed instead.
stdio: ['inherit', 'pipe', 'pipe'],
})
if (result.error !== undefined) throw result.error
@@ -101,11 +84,7 @@ export function run(command: string, args: readonly string[], options: RunOption
}
/**
* Whether this module is the process entry point.
*
* The release scripts are both commands and modules: a test imports their pure
* logic, and importing a module runs its body, so an unguarded `main()` would
* run the wrong command with the wrong arguments.
* Return whether Node started the given module as the process entry point.
* @param moduleUrl - the caller's `import.meta.url`.
* @returns True when Node started this module.
*/
+3 -3
View File
@@ -434,8 +434,8 @@ const VENDORED_LIBRARY = /^@deepseek-ai\\/(cosmokit|schemastery)(\\/|$)/
expect: 1,
},
{
// The framework peer is no longer a registry name, so the rehearsal must install this
// repository's vendored copies; cosmokit comes along as cordis's own dependency.
// The framework peer is a rescoped package, so the rehearsal installs this
// repository's vendored copies; cosmokit arrives as cordis's dependency.
id: 'packed-install-vendored-peer',
file: 'packages/sandbox/sandbox-local/tests/packed-install.e2e.ts',
find: ` 'packages/runtime-diagnostics/invariants',
@@ -552,7 +552,7 @@ function rewriteLine(line: string, file: string, all: readonly Pattern[]): strin
* Markdown splits in two. Every fence is code a reader copies or a
* configuration they mount, so every fence follows the rename regardless of its
* info string. Prose follows it only under `docs/`, where a sentence quoting
* `` `cordis` `` teaches a name this repository no longer resolves; elsewhere
* `` `cordis` `` teaches an unresolved package name; elsewhere
* prose is a record of what was true when it was written, and the same spelling
* can mean something else entirely — the Python SDK's `cordis` option, or the
* unvendored `@cordisjs/plugin-http`.
+3 -3
View File
@@ -180,9 +180,9 @@ for line in sys.stdin:
},
})
elif method == "tools/list":
# Keep discovery pending longer than the old smoke's 100 ms grace
# period. An SDK runtime that answers initialize too early will make
# its first model request without this tool and fail deterministically.
# Keep discovery pending long enough that an SDK runtime answering
# initialize before discovery completes makes its first model request
# without this tool and fails deterministically.
time.sleep(0.25)
send({
"jsonrpc": "2.0",
@@ -8,11 +8,11 @@
},
{
"role": "user",
"content": "# DeepSeek Harness\n\nEnglish | [中文](README.zh.md)\n\nDeepSeek Harness (`dsh`) is an open-source agent harness developed by [DeepSeek AI](https://deepseek.com).\n\nIt uses an architecture where **everything is a plugin**, and is powered by [Cordis](https://github.com/cordiverse/cordis), whose design is described in [_A Programming Paradigm for Spatiotemporal Composability_](https://github.com/cordiverse/paper).\n\n## Developer preview\n\nDeepSeek Harness is currently in _developer preview_ and is iterating rapidly. **THERE WILL BE COMPATIBILITY-BREAKING CHANGES.**\n\n## Run\n\n### Run from `npm`\n\nInstall `Node.js`, then run:\n\n```sh\nnpx @deepseek-ai/dsh web\n```\n\nThe command starts the Web UI at `http://127.0.0.1:3080` by default and opens it in the default browser for a local launch. An SSH launch only prints the host URL because the SSH client or editor owns the local forwarded address. Pass `--no-open` to run the server without opening a browser. See [Web UI guide](docs/user/guide/index.md).\n\n### Run from source\n\nTo run from a repository checkout:\n\n```sh\ngit clone https://github.com/deepseek-ai/deepseek-harness.git\ncd deepseek-harness\npnpm install\npnpm run build\npnpm dsh web\n```\n\n`pnpm run build` prepares the repository artifacts. `pnpm dsh web` uses those built artifacts without rebuilding.\n\n## Community and support\n\n- Feel free to submit feedback or bug reports through [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions).\n- Add the [`dsh-plugin`](https://github.com/topics/dsh-plugin) topic to your plugin repository for discoverability.\n- Join <a href=\"https://discord.gg/Ycq5dCaS4\">DeepSeek Harness Discord community</a>.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Development\n\nStart with the [development guide](docs/development.md) and [architecture documentation](docs/architecture.md).\n\nFor agents, follow [AGENTS.md](AGENTS.md).\n\n## License\n\n[MIT](LICENSE)\n\nThird-party dependencies and their licenses are disclosed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).\n"
"content": "# DeepSeek Harness\n\nEnglish | [中文](README.zh.md)\n\nDeepSeek Harness (`dsh`) is an open-source agent harness developed by [DeepSeek AI](https://deepseek.com).\n\nIt uses an architecture where **everything is a plugin**, and is powered by [Cordis](https://github.com/cordiverse/cordis), whose design is described in [_A Programming Paradigm for Spatiotemporal Composability_](https://github.com/cordiverse/paper).\n\nDocumentation: [https://deepseek-harness.github.io/deepseek-harness/](https://deepseek-harness.github.io/deepseek-harness/)\n\n## Developer preview\n\nDeepSeek Harness is currently in _developer preview_ and is iterating rapidly. **THERE WILL BE COMPATIBILITY-BREAKING CHANGES.**\n\n## Run\n\n### Run from `npm`\n\nInstall `Node.js`, then run:\n\n```sh\nnpx @deepseek-ai/dsh web\n```\n\nThe command starts the Web UI at `http://127.0.0.1:3080` by default and opens it in the default browser for a local launch. An SSH launch only prints the host URL because the SSH client or editor owns the local forwarded address. Pass `--no-open` to run the server without opening a browser. See [Web UI guide](docs/user/guide/index.md).\n\n### Run from source\n\nTo run from a repository checkout:\n\n```sh\ngit clone https://github.com/deepseek-ai/deepseek-harness.git\ncd deepseek-harness\npnpm install\npnpm run build\npnpm dsh web\n```\n\n`pnpm run build` prepares the repository artifacts. `pnpm dsh web` uses those built artifacts without rebuilding.\n\n## Community and support\n\n- Feel free to submit feedback or bug reports through [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions).\n- Add the [`dsh-plugin`](https://github.com/topics/dsh-plugin) topic to your plugin repository for discoverability.\n- Join <a href=\"https://discord.gg/Ycq5dCaS4\">DeepSeek Harness Discord community</a>.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Development\n\nStart with the [development guide](docs/development.md) and [architecture documentation](docs/architecture.md).\n\nFor agents, follow [AGENTS.md](AGENTS.md).\n\n## License\n\n[MIT](LICENSE)\n\nThird-party dependencies and their licenses are disclosed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).\n"
},
{
"role": "assistant",
"content": "# DeepSeek Harness\n\n[English](README.md) | 中文\n\nDeepSeek Harness`dsh`)是由 [DeepSeek AI](https://deepseek.com) 开发的开源 agent harness(智能体框架)。\n\n它采用**一切皆插件**的架构,并由 [Cordis](https://github.com/cordiverse/cordis) 驱动,其设计参见论文 [_A Programming Paradigm for Spatiotemporal Composability_](https://github.com/cordiverse/paper)。\n\n## 开发者预览\n\nDeepSeek Harness 目前处于 _开发者预览_ 阶段,正在快速迭代。**未来将出现破坏兼容性的变更。**\n\n<a id=\"run\"></a>\n\n## 运行\n\n### 通过 `npm` 运行\n\n安装 `Node.js`,然后运行:\n\n```sh\nnpx @deepseek-ai/dsh web\n```\n\n该命令默认会在 `http://127.0.0.1:3080` 启动 Web UI,本机启动时还会用默认浏览器打开页面。通过 SSH 启动时只打印宿主机 URL,因为本地转发地址由 SSH 客户端或编辑器持有。传入 `--no-open` 可仅运行服务器而不打开浏览器。详见 [Web UI 指南](docs/user/guide/index.zh.md)。\n\n<a id=\"run-from-source\"></a>\n\n### 从源码运行\n\n如需从仓库源码运行:\n\n```sh\ngit clone https://github.com/deepseek-ai/deepseek-harness.git\ncd deepseek-harness\npnpm install\npnpm run build\npnpm dsh web\n```\n\n`pnpm run build` 会准备仓库产物。`pnpm dsh web` 会直接使用这些已构建产物,不会重新构建。\n\n## 社区与支持\n\n- 欢迎通过 [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions) 提交反馈或 bug 报告。\n- 为你的插件仓库添加 [`dsh-plugin`](https://github.com/topics/dsh-plugin) 话题,便于被发现。\n- 欢迎加入 DeepSeek Harness 企微群:扫码添加企微小助手并填写入群问卷,完成后小助手会邀请你入群。\n\n<table>\n <thead>\n <tr>\n <th align=\"center\">企微小助手</th>\n <th align=\"center\">入群问卷</th>\n <th align=\"center\">微信公众号</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td align=\"center\"><img src=\"https://cdn.deepseek.com/harness/readme/community-wecom-assistant.png\" alt=\"DeepSeek Harness 企微小助手二维码\" width=\"180\" height=\"180\"></td>\n <td align=\"center\"><a href=\"https://trtgsjkv6r.feishu.cn/share/base/form/shrcnIt5twSVdLGD52KJBckGCgg\"><img src=\"https://cdn.deepseek.com/harness/readme/community-wecom-survey.png\" alt=\"DeepSeek Harness 入群问卷二维码\" width=\"180\" height=\"180\"></a></td>\n <td align=\"center\"><img src=\"https://cdn.deepseek.com/harness/readme/community-wechat-official-account.png\" alt=\"DeepSeek Harness 团队微信公众号二维码\" width=\"180\" height=\"180\"></td>\n </tr>\n </tbody>\n</table>\n\n## 参与贡献\n\n参见 [CONTRIBUTING.md](CONTRIBUTING.zh.md)。\n\n## 开发\n\n请先阅读[开发指南](docs/development.zh.md)与[架构文档](docs/architecture.zh.md)。\n\n面向 agent:请遵循 [AGENTS.md](AGENTS.md)。\n\n## 许可证\n\n[MIT](LICENSE)\n\n第三方依赖及其许可证见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。\n"
"content": "# DeepSeek Harness\n\n[English](README.md) | 中文\n\nDeepSeek Harness`dsh`)是由 [DeepSeek AI](https://deepseek.com) 开发的开源 agent harness(智能体框架)。\n\n它采用**一切皆插件**的架构,并由 [Cordis](https://github.com/cordiverse/cordis) 驱动,其设计参见论文 [_A Programming Paradigm for Spatiotemporal Composability_](https://github.com/cordiverse/paper)。\n\n文档:[https://deepseek-harness.github.io/deepseek-harness/](https://deepseek-harness.github.io/deepseek-harness/)\n\n## 开发者预览\n\nDeepSeek Harness 目前处于 _开发者预览_ 阶段,正在快速迭代。**未来将出现破坏兼容性的变更。**\n\n<a id=\"run\"></a>\n\n## 运行\n\n### 通过 `npm` 运行\n\n安装 `Node.js`,然后运行:\n\n```sh\nnpx @deepseek-ai/dsh web\n```\n\n该命令默认会在 `http://127.0.0.1:3080` 启动 Web UI,本机启动时还会用默认浏览器打开页面。通过 SSH 启动时只打印宿主机 URL,因为本地转发地址由 SSH 客户端或编辑器持有。传入 `--no-open` 可仅运行服务器而不打开浏览器。详见 [Web UI 指南](docs/user/guide/index.zh.md)。\n\n<a id=\"run-from-source\"></a>\n\n### 从源码运行\n\n如需从仓库源码运行:\n\n```sh\ngit clone https://github.com/deepseek-ai/deepseek-harness.git\ncd deepseek-harness\npnpm install\npnpm run build\npnpm dsh web\n```\n\n`pnpm run build` 会准备仓库产物。`pnpm dsh web` 会直接使用这些已构建产物,不会重新构建。\n\n## 社区与支持\n\n- 欢迎通过 [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions) 提交反馈或 bug 报告。\n- 为你的插件仓库添加 [`dsh-plugin`](https://github.com/topics/dsh-plugin) 话题,便于被发现。\n- 欢迎加入 DeepSeek Harness 企微群:扫码添加企微小助手并填写入群问卷,完成后小助手会邀请你入群。\n\n<table>\n <thead>\n <tr>\n <th align=\"center\">企微小助手</th>\n <th align=\"center\">入群问卷</th>\n <th align=\"center\">微信公众号</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td align=\"center\"><img src=\"https://cdn.deepseek.com/harness/readme/community-wecom-assistant.png\" alt=\"DeepSeek Harness 企微小助手二维码\" width=\"180\" height=\"180\"></td>\n <td align=\"center\"><a href=\"https://trtgsjkv6r.feishu.cn/share/base/form/shrcnIt5twSVdLGD52KJBckGCgg\"><img src=\"https://cdn.deepseek.com/harness/readme/community-wecom-survey.png\" alt=\"DeepSeek Harness 入群问卷二维码\" width=\"180\" height=\"180\"></a></td>\n <td align=\"center\"><img src=\"https://cdn.deepseek.com/harness/readme/community-wechat-official-account.png\" alt=\"DeepSeek Harness 团队微信公众号二维码\" width=\"180\" height=\"180\"></td>\n </tr>\n </tbody>\n</table>\n\n## 参与贡献\n\n参见 [CONTRIBUTING.md](CONTRIBUTING.zh.md)。\n\n## 开发\n\n请先阅读[开发指南](docs/development.zh.md)与[架构文档](docs/architecture.zh.md)。\n\n面向 agent:请遵循 [AGENTS.md](AGENTS.md)。\n\n## 许可证\n\n[MIT](LICENSE)\n\n第三方依赖及其许可证见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。\n"
},
{
"role": "user",
-2
View File
@@ -42,9 +42,7 @@ export function runGit(root: string, args: string[], operation: string, input?:
/** One regular stage-zero Git index entry and its exact blob bytes. */
export interface GitIndexBlob {
/** Object ID recorded in the index. */
objectId: string
/** Blob bytes stored under that object ID. */
content: Buffer
}
+15
View File
@@ -545,6 +545,11 @@
"symbol": "SessionLocation",
"source": "packages/session/session-persistence/src/index.ts"
},
{
"doc": "docs/subsystems/persistence.md",
"symbol": "SessionFormatMigration",
"source": "packages/session/session-persistence/src/format-decoder.ts"
},
{
"doc": "docs/subsystems/persistence.md",
"symbol": "SessionRawArtifact",
@@ -930,6 +935,16 @@
"symbol": "StoredImageAttachment",
"source": "packages/attachment/attachment/src/types.ts"
},
{
"doc": "docs/subsystems/attachment.md",
"symbol": "ImageRequestPolicy",
"source": "packages/attachment/attachment/src/types.ts"
},
{
"doc": "docs/subsystems/attachment.md",
"symbol": "RequestImageAttachment",
"source": "packages/attachment/attachment/src/types.ts"
},
{
"doc": "docs/subsystems/shell.md",
"symbol": "ShellExecRequest",
-19
View File
@@ -22,13 +22,9 @@ const CLIENT_WEB = '@deepseek-ai/dsh-client-web'
/** One workspace package's browser-module declaration. */
export interface ClientDeclaration {
/** npm package name. */
readonly name: string
/** Repository-relative package manifest. */
readonly manifest: string
/** Whether the manifest declares a dynamic dsh.client row. */
readonly dynamic: boolean
/** Exact module-table specifiers requested by the row. */
readonly external: readonly string[]
/** Informational package dependencies declared by the row. */
readonly inject: readonly string[]
@@ -36,43 +32,28 @@ export interface ClientDeclaration {
/** One package directly under packages/client. */
export interface ClientPackage extends ClientDeclaration {
/** Whether its build config uses the staticLinked preset. */
readonly staticLinked: boolean
/** Production source locations grouped by imported package name. */
readonly sourceUses: Readonly<Record<string, readonly string[]>>
/** Production source locations grouped by runtime-imported package name. */
readonly runtimeSourceUses: Readonly<Record<string, readonly string[]>>
/** Installed implementation dependencies. */
readonly dependencies: Readonly<Record<string, string>>
/** Consumer-supplied dependencies. */
readonly peerDependencies: Readonly<Record<string, string>>
/** Dependencies available while developing the package. */
readonly devDependencies: Readonly<Record<string, string>>
}
/** Complete source-plane input to the client package verifier. */
export interface ClientPackageFacts {
/** Packages directly under packages/client. */
readonly packages: readonly ClientPackage[]
/** Every workspace package, including packages without a browser row. */
readonly declarations: readonly ClientDeclaration[]
/** Packages whose build config uses the staticLinked preset. */
readonly staticLinkedPackages: ReadonlySet<string>
/** Specifiers the web shell seeds into the module table. */
readonly platformModules: readonly string[]
/** Dynamic factories the HTML parser loads before shell boot. */
readonly preloadedExternals: readonly string[]
/** Package rows whose bundles the HTML parser executes before shell boot. */
readonly parserPreloadIds: readonly string[]
/** Manifest field errors found while reading declarations. */
readonly malformed: readonly string[]
}
/** Result of reading every workspace browser-module declaration. */
export interface ClientDeclarations {
/** One declaration record per named workspace manifest. */
readonly declarations: ClientDeclaration[]
/** Manifest field errors that prevent a reliable declaration. */
readonly malformed: string[]
}
-10
View File
@@ -22,37 +22,28 @@ const PROTOCOL_EXPORTS = new Set(['Config', 'inject', 'name', 'reusable', 'apply
/** Per-file walk state threaded through the scope recursion. */
interface Walk {
/** Repo-relative path of the file being walked. */
rel: string
/** The parsed source file. */
sf: ts.SourceFile
/** Raw file text (rawJsDoc reads comment ranges out of it). */
text: string
/** The program's checker, consulted only for heritage-member lookups. */
checker: ts.TypeChecker
/** The aggregate violation list, appended in place. */
violations: string[]
}
/** True when a statement carries the `export` modifier. */
function isExported(stmt: ts.Statement): boolean {
return ts.canHaveModifiers(stmt) && (ts.getModifiers(stmt)?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false)
}
/** True for a class member a consumer cannot reach: `private`/`protected`/`#name`. */
function isNonPublic(member: ts.ClassElement): boolean {
const mods = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined
return (mods?.some(m => m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword) ?? false)
|| ('name' in member && ts.isPrivateIdentifier(member.name))
}
/** True when a class member carries the `static` modifier. */
function isStatic(member: ts.ClassElement): boolean {
const mods = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined
return mods?.some(m => m.kind === ts.SyntaxKind.StaticKeyword) ?? false
}
/** The `this`-receiver exemption every function-like check shares. */
function thisReceiver(p: ts.ParameterDeclaration): boolean {
return ts.isIdentifier(p.name) && p.name.text === 'this'
}
@@ -611,7 +602,6 @@ function main(): void {
process.exit(1)
}
// Run only when invoked as a script, not when imported by a test.
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
main()
}
-1
View File
@@ -195,7 +195,6 @@ export function findViolations(
return out
}
// Run only when invoked as a script, not when imported by the spec.
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
// Archived notes remain valid link targets, but their historical outbound links are frozen.
const files = uniqueRepoFiles(root, PATTERNS, isArchivedAgentNotePath)
@@ -208,7 +208,6 @@ function main(): void {
process.exit(1)
}
// Run only when invoked as a script, not when imported by a test.
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
main()
}
@@ -53,6 +53,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/code-runtime/code-runtime-worker-thread': { kind: 'indirect', reason: 'The worker backend delegates model rendering to Code Mode in dsh-tools.' },
'packages/code-runtime/code-runtime-python': { kind: 'indirect', reason: 'The CPython subprocess backend delegates model rendering to Code Mode in dsh-tools.' },
'packages/client/ui-agent-preset': { kind: 'indirect', reason: 'Browser-side settings row; the preset it selects owns every model-facing effect.' },
'packages/util/crypto': { kind: 'indirect', reason: 'Pure identifier minting; the ids consumers mint with it never enter prompts as semantic content.' },
'packages/core/agent-default-model': { kind: 'indirect', reason: 'The service supplies a ModelSelection; request assembly and adapters own the model-visible request.' },
'packages/preset/agent-presets': { kind: 'indirect', reason: 'The mount installs a preset\'s own plugins, which own every model-facing registration it makes visible.' },
'packages/typert/registry': { kind: 'none', reason: 'Runtime type registry; consumers (cordis_inspect, wire faces, gates) own any model-visible projection of registry contents.' },
@@ -61,6 +62,8 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/client/hmr': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/modules': { kind: 'none', reason: 'Browser-side module-loading kernel machinery; registers nothing model-facing.' },
'packages/test-support/client-runtime': { kind: 'none', reason: 'Browser-side test infrastructure (jsdom bench); registers nothing model-facing.' },
'packages/experimental/webworker-runtime': { kind: 'none', reason: 'Browser-side host runtime and Node-compatibility layer; the plugins it boots own every model-facing registration.' },
'packages/experimental/webworker-packer': { kind: 'none', reason: 'Build-time image writer; its output reaches a model only through the tree the worker then boots.' },
'packages/client/ui-slots': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-attachment': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-primitives': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },