@deepseek-ai/dsh-tool-workspace-dependencies 0.1.7-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +128 -0
- package/README.zh.md +128 -0
- package/lib/index.js +252 -0
- package/lib/types/index.d.ts +86 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/skill/tool-workspace-dependencies/README.md
|
|
5
|
+
README.md: 1f7147a1febeb0210befe64dc462e2527013dab3
|
|
6
|
+
README.zh.md: f2a1934256776ef97d414be4709a6b918989ae35
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "The load_workspace_dependencies tool: absolute paths into a bundled Python, Node.js, and pnpm payload, used in place or installed under the Harness home."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-tool-workspace-dependencies
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Deployments that ship their own script runtimes (Desktop's primary runtime, or a container image layer) mount this tool so the agent can ask where the bundled Python, Node.js, and pnpm live instead of discovering a system interpreter. The tool returns absolute paths and recorded distribution versions; it changes neither `PATH` nor package-manager settings. The payload is either copied under the Harness home on first use (Desktop) or used where it lies (read-only carriers).
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
Mount the plugin beside the tool registry with the payload directory. Configuration validation requires a nonempty `source` and rejects empty `root` values before activation; both paths must be absolute. The bundled Office skills (`@deepseek-ai/dsh-skill-office`) reference this tool by name for their default interpreter.
|
|
29
|
+
|
|
30
|
+
### Minimal configuration
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
- name: '@deepseek-ai/dsh-tool-workspace-dependencies'
|
|
34
|
+
config:
|
|
35
|
+
source: /path/to/primary-runtime
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| Field | Default | Meaning |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `source` | required | Absolute payload directory carrying `runtime.json` and `dependencies/`. |
|
|
41
|
+
| `root` | unset | Absolute installation directory under the Harness home. Set: the payload is copied there on the first call and reused while `runtime.json` is unchanged. Unset: the payload is validated and used in place; nothing is copied. |
|
|
42
|
+
|
|
43
|
+
### Payload layout
|
|
44
|
+
|
|
45
|
+
`runtime.json` records `desktopVersion`, `platform` (`win32`, `darwin`, or `linux`), `arch`, optional `payloadDigest`, top-level `python`, optional `node`/`pnpm` versions, and the complete `pythonPackages` distribution-version map. A pnpm entry requires Node.js. Python libraries, including numpy and pandas, appear only in `pythonPackages`. Entries live under `dependencies/`: `python/bin/python3` (`python/python.exe` on Windows) with `site-packages` beneath it, and, when declared, `node/bin/node` with `node/node_modules` and `pnpm/bin/pnpm.mjs`. A manifest whose platform or architecture differs from the running process is rejected.
|
|
46
|
+
|
|
47
|
+
The packaged `sdk` profile uses its bundled Python and Office skills by default; `DSH_PRIMARY_RUNTIME` overrides the resource location, and an empty value opts out. Source launches without a carrier default remain opt-in. See [runtime configuration](../../../python/sdk-runtime/README.md) for independent skill selection. Missing skill resources produce a startup warning; invalid or incomplete external runtime payloads fail the first tool call. Profile configuration changes require restarting the SDK process.
|
|
48
|
+
|
|
49
|
+
### Build a carrier payload
|
|
50
|
+
|
|
51
|
+
From a repository checkout with dependencies installed, `CI=true pnpm run prepare:primary-runtime --target linux-x64 --output /tmp/dsh-office` writes `primary-runtime/` and `office-skills/`. The shared [download lock](../../../scripts/primary-runtime/lock.json) also covers `linux-arm64`, `mac-arm64`, `mac-x64`, and `win-x64`. `--python-only` omits Node.js and pnpm; `--cache` selects the hash-verified archive cache. The entry executes interpreter and Office read/write checks only for a native target. Cross-target builds require those checks on the target host before deployment.
|
|
52
|
+
|
|
53
|
+
A container can copy both directories into an immutable image layer and set `DSH_PRIMARY_RUNTIME` to the absolute `primary-runtime/` path. The SDK queries that payload in place. Desktop uses the same builder and retains its Harness-home installation and signing checks.
|
|
54
|
+
|
|
55
|
+
-----
|
|
56
|
+
|
|
57
|
+
<a id="understand-the-implementation"></a>
|
|
58
|
+
## Understand the implementation
|
|
59
|
+
|
|
60
|
+
<details>
|
|
61
|
+
<summary>Implementation internals — click to expand</summary>
|
|
62
|
+
|
|
63
|
+
`readPrimaryRuntime` and build smoke checks share `parsePrimaryRuntime`. It validates the flat manifest and rejects duplicate normalized distribution names. Legacy `components` metadata is normalized in memory, retaining its consistency checks; a missing legacy distribution map becomes empty. Mixed flat and legacy version fields are rejected. Reads do not rewrite metadata, and equivalent normalized manifests can reuse an installed payload. `workspaceDependencyPaths` derives the platform-specific entries. `installPrimaryRuntime` copies into a staging directory, requires declared interpreters and scripts to be files and package roots to be directories, and swaps it into place while retaining the previous tree on failure; `resolvePrimaryRuntime` verifies the same entries without copying. The tool memoizes the first successful preparation for the plugin's lifetime.
|
|
64
|
+
|
|
65
|
+
| File | Responsibility |
|
|
66
|
+
|---|---|
|
|
67
|
+
| [`src/index.ts`](src/index.ts) | Manifest validation, path derivation, in-place and installed preparation, tool registration. |
|
|
68
|
+
| — | No runtime invariant companion is published: the payload manifest is validated on every preparation, and the tool registry owns registration lifecycle. |
|
|
69
|
+
|
|
70
|
+
</details>
|
|
71
|
+
|
|
72
|
+
-----
|
|
73
|
+
|
|
74
|
+
<a id="further-exploration"></a>
|
|
75
|
+
## Further Exploration
|
|
76
|
+
|
|
77
|
+
- [Office skills](../skill-office/README.md) — the workflows that call this tool for their interpreter.
|
|
78
|
+
- [Tool registry](../../core/tools/README.md) — registration and schemas.
|
|
79
|
+
|
|
80
|
+
-----
|
|
81
|
+
|
|
82
|
+
<a id="model-experience"></a>
|
|
83
|
+
## Model Experience
|
|
84
|
+
|
|
85
|
+
### Tool schema
|
|
86
|
+
|
|
87
|
+
#### What the model sees
|
|
88
|
+
|
|
89
|
+
The model sees the generated [`load_workspace_dependencies` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-workspace-dependencies).
|
|
90
|
+
|
|
91
|
+
#### Token effect
|
|
92
|
+
|
|
93
|
+
Fixed schema cost per request where the tool is visible; the description names the bundled Office libraries so the model can choose the interpreter without loading a skill first.
|
|
94
|
+
|
|
95
|
+
#### KV Cache effect
|
|
96
|
+
|
|
97
|
+
Prefix-stable while the tool definition and visibility are unchanged.
|
|
98
|
+
|
|
99
|
+
### Tool result
|
|
100
|
+
|
|
101
|
+
#### What the model sees
|
|
102
|
+
|
|
103
|
+
One JSON object with absolute `python` and `pythonPackages` paths, `pythonDistributions` from `runtime.json`, and `node`, `nodePackages`, and `pnpm` when the payload declares them. Repeated calls return the same object.
|
|
104
|
+
|
|
105
|
+
#### Token effect
|
|
106
|
+
|
|
107
|
+
A few hundred characters per call; paths dominate.
|
|
108
|
+
|
|
109
|
+
#### KV Cache effect
|
|
110
|
+
|
|
111
|
+
Append-only tool result in the turn history; no prompt section is added.
|
|
112
|
+
|
|
113
|
+
## Known Limitations and Deferred Work
|
|
114
|
+
|
|
115
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
116
|
+
|
|
117
|
+
- Linux targets require glibc; musl payloads are not locked.
|
|
118
|
+
- Windows payloads used in place must already be executable from their carrier; the in-place mode performs no permission repair.
|
|
119
|
+
|
|
120
|
+
<a id="dev-note"></a>
|
|
121
|
+
### Dev Note
|
|
122
|
+
|
|
123
|
+
<details>
|
|
124
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
125
|
+
|
|
126
|
+
Placement and carrier choices are recorded in the [shared-runtime Agent Note](../../../.agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.md).
|
|
127
|
+
|
|
128
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "load_workspace_dependencies 工具:返回随包附带的 Python、Node.js 与 pnpm 的绝对路径,payload 可原地使用或安装到 Harness home。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-tool-workspace-dependencies
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
自带脚本运行时的部署(Desktop 的 primary runtime,或容器镜像层)挂载本工具,让 agent 询问随包的 Python、Node.js 与 pnpm 在哪里,而不是自行寻找系统解释器。工具返回绝对路径与记录的发行版版本,不改 `PATH`,不改包管理器设置。payload 或在首次调用时复制到 Harness home 下(Desktop),或原地使用(只读载体)。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [了解实现](#understand-the-implementation)
|
|
18
|
+
- [延伸阅读](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与暂缓事项](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
与工具注册表一起挂载,给出 payload 目录。配置校验在激活前要求非空 `source` 并拒绝空 `root`;两条路径都必须为绝对路径。内置 Office skills(`@deepseek-ai/dsh-skill-office`)按名字引用本工具取默认解释器。
|
|
29
|
+
|
|
30
|
+
### 最小配置
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
- name: '@deepseek-ai/dsh-tool-workspace-dependencies'
|
|
34
|
+
config:
|
|
35
|
+
source: /path/to/primary-runtime
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| 字段 | 默认值 | 含义 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `source` | 必填 | 含 `runtime.json` 与 `dependencies/` 的 payload 绝对目录。 |
|
|
41
|
+
| `root` | 未设置 | Harness home 下的绝对安装目录。设置时首次调用把 payload 复制过去,`runtime.json` 不变则复用;未设置时校验后原地使用,不复制。 |
|
|
42
|
+
|
|
43
|
+
### payload 布局
|
|
44
|
+
|
|
45
|
+
`runtime.json` 记录 `desktopVersion`、`platform`(`win32`、`darwin` 或 `linux`)、`arch`、可选的 `payloadDigest`、顶层 `python` 与可选的 `node`/`pnpm` 版本,以及完整的 `pythonPackages` 分发包版本表。声明 pnpm 时必须同时声明 Node.js。numpy、pandas 等 Python 库只出现在 `pythonPackages` 中。条目位于 `dependencies/`:`python/bin/python3`(Windows 为 `python/python.exe`)及其下的 `site-packages`;声明了才有的 `node/bin/node`、`node/node_modules` 与 `pnpm/bin/pnpm.mjs`。平台或架构与当前进程不符的清单被拒绝。
|
|
46
|
+
|
|
47
|
+
打包的 `sdk` profile 默认使用随包 Python 和 Office skills;`DSH_PRIMARY_RUNTIME` 覆盖资源位置,空值表示禁用。没有载体默认路径的源码启动仍需显式启用。独立选择 skills 的方式见[运行时配置](../../../python/sdk-runtime/README.zh.md)。缺少 skill 资源会产生启动警告;无效或不完整的外部运行时 payload 在首次工具调用时失败。profile 配置变更需要重启 SDK 进程。
|
|
48
|
+
|
|
49
|
+
### 构建载体 payload
|
|
50
|
+
|
|
51
|
+
在已安装依赖的仓库 checkout 中运行 `CI=true pnpm run prepare:primary-runtime --target linux-x64 --output /tmp/dsh-office`,会生成 `primary-runtime/` 和 `office-skills/`。[共享下载锁](../../../scripts/primary-runtime/lock.json)还覆盖 `linux-arm64`、`mac-arm64`、`mac-x64` 和 `win-x64`。`--python-only` 省略 Node.js 和 pnpm;`--cache` 选择经过哈希校验的归档缓存。入口仅对本机目标执行解释器与 Office 读写检查。跨目标构建必须在部署前到目标主机执行这些检查。
|
|
52
|
+
|
|
53
|
+
容器可将这两个目录复制到不可变镜像层,并将 `DSH_PRIMARY_RUNTIME` 设为 `primary-runtime/` 的绝对路径。SDK 原位查询该 payload。Desktop 使用同一构建器,并保留 Harness-home 安装与签名检查。
|
|
54
|
+
|
|
55
|
+
-----
|
|
56
|
+
|
|
57
|
+
<a id="understand-the-implementation"></a>
|
|
58
|
+
## 了解实现
|
|
59
|
+
|
|
60
|
+
<details>
|
|
61
|
+
<summary>实现细节——点击展开</summary>
|
|
62
|
+
|
|
63
|
+
`readPrimaryRuntime` 与构建冒烟检查共用 `parsePrimaryRuntime`。它校验扁平清单,拒绝归一化后重名的分发包。旧 `components` 元数据在内存中归一化,并保留旧格式的一致性校验;旧分发包版本表缺失时转换为空表。混用顶层与旧组件版本字段会被拒绝。读取不改写元数据,归一化后等价的清单可复用已安装产物。`workspaceDependencyPaths` 推导各平台的条目路径。`installPrimaryRuntime` 复制到暂存目录、要求声明的解释器和脚本为文件、库路径为目录、再换入正式位置,失败时保留旧树;`resolvePrimaryRuntime` 做同样的校验但不复制。工具在插件生命周期内记住首次成功的结果。
|
|
64
|
+
|
|
65
|
+
| 文件 | 职责 |
|
|
66
|
+
|---|---|
|
|
67
|
+
| [`src/index.ts`](src/index.ts) | 清单校验、路径推导、原地与安装两种准备方式、工具注册。 |
|
|
68
|
+
| — | 不发布运行时不变量伴随入口:每次准备都校验 payload 清单,工具注册表负责注册生命周期。 |
|
|
69
|
+
|
|
70
|
+
</details>
|
|
71
|
+
|
|
72
|
+
-----
|
|
73
|
+
|
|
74
|
+
<a id="further-exploration"></a>
|
|
75
|
+
## 延伸阅读
|
|
76
|
+
|
|
77
|
+
- [Office skills](../skill-office/README.zh.md)——调用本工具取解释器的工作流。
|
|
78
|
+
- [工具注册表](../../core/tools/README.zh.md)——注册与 schema。
|
|
79
|
+
|
|
80
|
+
-----
|
|
81
|
+
|
|
82
|
+
<a id="model-experience"></a>
|
|
83
|
+
## 模型体验
|
|
84
|
+
|
|
85
|
+
### 工具 schema
|
|
86
|
+
|
|
87
|
+
#### 模型看到什么
|
|
88
|
+
|
|
89
|
+
模型看到生成的 [`load_workspace_dependencies` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-workspace-dependencies)。
|
|
90
|
+
|
|
91
|
+
#### Token 影响
|
|
92
|
+
|
|
93
|
+
工具可见的每个请求固定一份 schema 开销;描述里列出了随包的 Office 库,模型不必先加载 skill 就能选择解释器。
|
|
94
|
+
|
|
95
|
+
#### KV 缓存影响
|
|
96
|
+
|
|
97
|
+
工具定义与可见性不变时前缀稳定。
|
|
98
|
+
|
|
99
|
+
### 工具结果
|
|
100
|
+
|
|
101
|
+
#### 模型看到什么
|
|
102
|
+
|
|
103
|
+
一个 JSON 对象:绝对路径 `python` 与 `pythonPackages`、来自 `runtime.json` 的 `pythonDistributions`,以及 payload 声明了才有的 `node`、`nodePackages` 与 `pnpm`。重复调用返回同一对象。
|
|
104
|
+
|
|
105
|
+
#### Token 影响
|
|
106
|
+
|
|
107
|
+
每次调用几百个字符,以路径为主。
|
|
108
|
+
|
|
109
|
+
#### KV 缓存影响
|
|
110
|
+
|
|
111
|
+
作为工具结果追加进轮次历史;不增加提示词分区。
|
|
112
|
+
|
|
113
|
+
## 已知限制与暂缓事项
|
|
114
|
+
|
|
115
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
116
|
+
|
|
117
|
+
- Linux 目标需要 glibc;尚未锁定 musl payload。
|
|
118
|
+
- Windows 上原地使用的 payload 须在载体里已可执行;原地模式不修复权限。
|
|
119
|
+
|
|
120
|
+
<a id="dev-note"></a>
|
|
121
|
+
### 开发备注
|
|
122
|
+
|
|
123
|
+
<details>
|
|
124
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
125
|
+
|
|
126
|
+
包归属与载体选择记录在[共享运行时 Agent Note](../../../.agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.zh.md) 中。
|
|
127
|
+
|
|
128
|
+
</details>
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { cp, lstat, mkdir, mkdtemp, readFile, rename, rm, stat } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
3
|
+
import z from "@deepseek-ai/schemastery";
|
|
4
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
5
|
+
//#region lib/types/index.js
|
|
6
|
+
/** Model-facing query for a bundled Python, Node.js, and pnpm payload, in place or installed under the Harness home. */
|
|
7
|
+
/** Cordis plugin identity. */
|
|
8
|
+
const name = "tool-workspace-dependencies";
|
|
9
|
+
/** Registry the tool registers into. */
|
|
10
|
+
const inject = ["tools"];
|
|
11
|
+
/** Require a named payload source before the Loader activates the tool. */
|
|
12
|
+
const Config = z.object({
|
|
13
|
+
source: z.string().min(1).required(),
|
|
14
|
+
root: z.string().min(1)
|
|
15
|
+
});
|
|
16
|
+
const VERSION = /^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/u;
|
|
17
|
+
const PLATFORMS = [
|
|
18
|
+
"win32",
|
|
19
|
+
"darwin",
|
|
20
|
+
"linux"
|
|
21
|
+
];
|
|
22
|
+
function isVersion(value) {
|
|
23
|
+
return typeof value === "string" && VERSION.test(value);
|
|
24
|
+
}
|
|
25
|
+
function isRecord(value) {
|
|
26
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
function isDistributionMap(value) {
|
|
29
|
+
return isRecord(value) && Object.entries(value).every(([name, version]) => /^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(name) && typeof version === "string" && /^\d[\w.!+-]*$/u.test(version));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Validate payload JSON and normalize legacy component fields to top-level versions.
|
|
33
|
+
* @param value - Untrusted decoded runtime.json contents.
|
|
34
|
+
* @returns Canonical metadata with one Python distribution map and no components field.
|
|
35
|
+
* @throws For malformed or mixed formats, duplicate distribution names, or inconsistent legacy metadata.
|
|
36
|
+
*/
|
|
37
|
+
function parsePrimaryRuntime(value) {
|
|
38
|
+
if (!isRecord(value)) throw new Error("primary runtime: invalid metadata");
|
|
39
|
+
const legacy = value.components !== void 0;
|
|
40
|
+
const versions = legacy ? value.components : value;
|
|
41
|
+
if (!isRecord(versions) || legacy && [
|
|
42
|
+
"python",
|
|
43
|
+
"node",
|
|
44
|
+
"pnpm"
|
|
45
|
+
].some((key) => value[key] !== void 0)) throw new Error("primary runtime: invalid metadata");
|
|
46
|
+
const { desktopVersion, platform, arch, payloadDigest } = value;
|
|
47
|
+
const { python, node, pnpm } = versions;
|
|
48
|
+
const pythonPackages = value.pythonPackages === void 0 && legacy ? {} : value.pythonPackages;
|
|
49
|
+
if (typeof desktopVersion !== "string" || desktopVersion.length === 0 || typeof platform !== "string" || !PLATFORMS.includes(platform) || typeof arch !== "string" || !["x64", "arm64"].includes(arch) || !isVersion(python) || node !== void 0 && !isVersion(node) || pnpm !== void 0 && (!isVersion(pnpm) || node === void 0) || payloadDigest !== void 0 && (typeof payloadDigest !== "string" || !/^[a-f0-9]{64}$/u.test(payloadDigest)) || !isDistributionMap(pythonPackages)) throw new Error("primary runtime: invalid metadata");
|
|
50
|
+
const entries = Object.entries(pythonPackages);
|
|
51
|
+
const distributions = new Map(entries.map(([name, version]) => [name.toLowerCase().replace(/[-_.]+/gu, "-"), version]));
|
|
52
|
+
if (distributions.size !== entries.length) throw new Error("primary runtime: invalid metadata");
|
|
53
|
+
if (legacy) for (const name of ["numpy", "pandas"]) {
|
|
54
|
+
if (!isVersion(versions[name])) throw new Error("primary runtime: invalid metadata");
|
|
55
|
+
const version = distributions.get(name);
|
|
56
|
+
if (version !== void 0 && version !== versions[name]) throw new Error(`primary runtime: conflicting ${name} distribution version`);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
desktopVersion,
|
|
60
|
+
platform,
|
|
61
|
+
arch,
|
|
62
|
+
...payloadDigest === void 0 ? {} : { payloadDigest },
|
|
63
|
+
python,
|
|
64
|
+
...node === void 0 ? {} : { node },
|
|
65
|
+
...pnpm === void 0 ? {} : { pnpm },
|
|
66
|
+
pythonPackages
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Read and normalize build metadata without rewriting the source file.
|
|
71
|
+
* @param root - Installed or bundled primary runtime directory.
|
|
72
|
+
* @returns Validated top-level versions and target identifiers, including for legacy components manifests.
|
|
73
|
+
*/
|
|
74
|
+
async function readPrimaryRuntime(root) {
|
|
75
|
+
return parsePrimaryRuntime(JSON.parse(await readFile(join(root, "runtime.json"), "utf8")));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Resolve platform-specific interpreter and library locations without changing the environment.
|
|
79
|
+
* @param root - Absolute payload or installation directory.
|
|
80
|
+
* @param manifest - Validated runtime metadata.
|
|
81
|
+
* @returns Absolute paths for explicit script execution and recorded bundled Python versions.
|
|
82
|
+
*/
|
|
83
|
+
function workspaceDependencyPaths(root, manifest) {
|
|
84
|
+
const dependencies = join(root, "dependencies");
|
|
85
|
+
const windows = manifest.platform === "win32";
|
|
86
|
+
const node = manifest.node === void 0 ? {} : {
|
|
87
|
+
node: join(dependencies, "node", "bin", windows ? "node.exe" : "node"),
|
|
88
|
+
nodePackages: join(dependencies, "node", "node_modules")
|
|
89
|
+
};
|
|
90
|
+
const pnpm = manifest.pnpm === void 0 ? {} : { pnpm: join(dependencies, "pnpm", "bin", "pnpm.mjs") };
|
|
91
|
+
return {
|
|
92
|
+
python: join(dependencies, "python", ...windows ? ["python.exe"] : ["bin", "python3"]),
|
|
93
|
+
...node,
|
|
94
|
+
...pnpm,
|
|
95
|
+
pythonPackages: join(dependencies, "python", ...windows ? ["Lib"] : ["lib", `python${manifest.python.split(".").slice(0, 2).join(".")}`], "site-packages"),
|
|
96
|
+
pythonDistributions: manifest.pythonPackages
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function validatePayloadEntries(paths) {
|
|
100
|
+
for (const [path, kind] of [
|
|
101
|
+
[paths.python, "file"],
|
|
102
|
+
[paths.node, "file"],
|
|
103
|
+
[paths.pnpm, "file"],
|
|
104
|
+
[paths.pythonPackages, "directory"],
|
|
105
|
+
[paths.nodePackages, "directory"]
|
|
106
|
+
]) {
|
|
107
|
+
if (path === void 0) continue;
|
|
108
|
+
const entry = await stat(path);
|
|
109
|
+
if (!(kind === "file" ? entry.isFile() : entry.isDirectory())) throw new Error(`primary runtime: expected ${kind} at ${path}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function exists(path) {
|
|
113
|
+
try {
|
|
114
|
+
if ((await lstat(path)).isSymbolicLink()) throw new Error(`primary runtime: installation path is a filesystem link: ${path}`);
|
|
115
|
+
return true;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error.code === "ENOENT") return false;
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function compatibleManifest(source) {
|
|
122
|
+
const manifest = await readPrimaryRuntime(source);
|
|
123
|
+
if (manifest.platform !== process.platform || manifest.arch !== process.arch) throw new Error("primary runtime: incompatible platform or architecture");
|
|
124
|
+
return manifest;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Use a payload where it lies: validate its metadata and entries, copying nothing.
|
|
128
|
+
* @param source - Payload directory, typically a read-only carrier.
|
|
129
|
+
* @returns Paths into the payload itself.
|
|
130
|
+
*/
|
|
131
|
+
async function resolvePrimaryRuntime(source) {
|
|
132
|
+
const paths = workspaceDependencyPaths(source, await compatibleManifest(source));
|
|
133
|
+
await validatePayloadEntries(paths);
|
|
134
|
+
return paths;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Install the application-owned payload locally, retaining a complete previous tree on copy failure.
|
|
138
|
+
* @param source - Payload carried by the current installation.
|
|
139
|
+
* @param root - Fixed primary runtime directory under the Harness home.
|
|
140
|
+
* @returns Paths into the installed payload; no PATH or package-manager configuration is changed.
|
|
141
|
+
*/
|
|
142
|
+
async function installPrimaryRuntime(source, root) {
|
|
143
|
+
const manifest = await compatibleManifest(source);
|
|
144
|
+
await mkdir(dirname(root), { recursive: true });
|
|
145
|
+
const previous = `${root}.previous`;
|
|
146
|
+
const previousExists = await exists(previous);
|
|
147
|
+
if (!await exists(root) && previousExists) await rename(previous, root);
|
|
148
|
+
if (await exists(join(root, "runtime.json")) && JSON.stringify(await readPrimaryRuntime(root)) === JSON.stringify(manifest)) {
|
|
149
|
+
const paths = workspaceDependencyPaths(root, manifest);
|
|
150
|
+
await validatePayloadEntries(paths);
|
|
151
|
+
return paths;
|
|
152
|
+
}
|
|
153
|
+
const staging = await mkdtemp(join(dirname(root), ".primary-runtime-"));
|
|
154
|
+
try {
|
|
155
|
+
await cp(source, staging, {
|
|
156
|
+
recursive: true,
|
|
157
|
+
dereference: true
|
|
158
|
+
});
|
|
159
|
+
await validatePayloadEntries(workspaceDependencyPaths(staging, manifest));
|
|
160
|
+
await rm(previous, {
|
|
161
|
+
recursive: true,
|
|
162
|
+
force: true
|
|
163
|
+
});
|
|
164
|
+
const replacing = await exists(root);
|
|
165
|
+
if (replacing) await rename(root, previous);
|
|
166
|
+
try {
|
|
167
|
+
await rename(staging, root);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (replacing) await rename(previous, root);
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
await rm(previous, {
|
|
173
|
+
recursive: true,
|
|
174
|
+
force: true
|
|
175
|
+
});
|
|
176
|
+
} finally {
|
|
177
|
+
await rm(staging, {
|
|
178
|
+
recursive: true,
|
|
179
|
+
force: true
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return workspaceDependencyPaths(root, manifest);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Register the read-only path query; the first invocation prepares (or merely validates) the payload.
|
|
186
|
+
* @param ctx - Tool registry owner.
|
|
187
|
+
* @param config - Payload location and optional installation directory.
|
|
188
|
+
*/
|
|
189
|
+
function apply(ctx, config) {
|
|
190
|
+
if (!isAbsolute(config.source) || config.root !== void 0 && !isAbsolute(config.root)) throw new Error("workspace dependencies: source and root must be absolute paths");
|
|
191
|
+
let preparation;
|
|
192
|
+
ctx.effect(() => async () => {
|
|
193
|
+
await preparation?.catch(() => void 0);
|
|
194
|
+
});
|
|
195
|
+
ctx.tools.register(defineTool({
|
|
196
|
+
name: "load_workspace_dependencies",
|
|
197
|
+
description: "Get absolute paths to bundled Python and library directories, plus bundled Python distribution versions. Node.js and pnpm paths are included when the payload provides them. Python includes numpy, pandas, python-docx, python-pptx, openpyxl, Pillow, lxml, and XlsxWriter. Use these libraries for Office files unless the user or workspace instructions select another environment. When Node.js and pnpm paths are returned, run pnpm with that Node executable and pnpm script path. This does not change PATH or package-manager settings.",
|
|
198
|
+
parameters: {},
|
|
199
|
+
output: {
|
|
200
|
+
schema: {
|
|
201
|
+
type: "object",
|
|
202
|
+
additionalProperties: false,
|
|
203
|
+
properties: {
|
|
204
|
+
python: {
|
|
205
|
+
type: "string",
|
|
206
|
+
required: true
|
|
207
|
+
},
|
|
208
|
+
node: {
|
|
209
|
+
type: "string",
|
|
210
|
+
description: "Absent when the payload ships no Node.js."
|
|
211
|
+
},
|
|
212
|
+
pnpm: {
|
|
213
|
+
type: "string",
|
|
214
|
+
description: "Absent when the payload ships no pnpm."
|
|
215
|
+
},
|
|
216
|
+
pythonPackages: {
|
|
217
|
+
type: "string",
|
|
218
|
+
required: true
|
|
219
|
+
},
|
|
220
|
+
nodePackages: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description: "Absent when the payload ships no Node.js."
|
|
223
|
+
},
|
|
224
|
+
pythonDistributions: {
|
|
225
|
+
type: "object",
|
|
226
|
+
additionalProperties: true,
|
|
227
|
+
required: true,
|
|
228
|
+
description: "Bundled distribution names and versions recorded in runtime.json; excludes user-installed additions."
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
render: (_args, value) => [{
|
|
233
|
+
type: "text",
|
|
234
|
+
text: JSON.stringify(value, void 0, 2)
|
|
235
|
+
}]
|
|
236
|
+
},
|
|
237
|
+
execute: () => {
|
|
238
|
+
preparation ??= (config.root === void 0 ? resolvePrimaryRuntime(config.source) : installPrimaryRuntime(config.source, config.root)).catch((error) => {
|
|
239
|
+
preparation = void 0;
|
|
240
|
+
throw error;
|
|
241
|
+
});
|
|
242
|
+
return preparation;
|
|
243
|
+
},
|
|
244
|
+
presentCall: () => ({
|
|
245
|
+
card: "generic",
|
|
246
|
+
title: "Load workspace dependencies",
|
|
247
|
+
kind: "read"
|
|
248
|
+
})
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
export { Config, apply, inject, installPrimaryRuntime, name, parsePrimaryRuntime, readPrimaryRuntime, resolvePrimaryRuntime, workspaceDependencyPaths };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Model-facing query for a bundled Python, Node.js, and pnpm payload, in place or installed under the Harness home. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
import z from '@deepseek-ai/schemastery';
|
|
4
|
+
/** Cordis plugin identity. */
|
|
5
|
+
export declare const name = "tool-workspace-dependencies";
|
|
6
|
+
/** Registry the tool registers into. */
|
|
7
|
+
export declare const inject: string[];
|
|
8
|
+
/** Payload location and optional installation directory. */
|
|
9
|
+
export interface Config {
|
|
10
|
+
/** Payload directory carrying `runtime.json` and `dependencies/`. */
|
|
11
|
+
readonly source: string;
|
|
12
|
+
/**
|
|
13
|
+
* Installation directory under the Harness home. When set, the payload is copied there on the
|
|
14
|
+
* first call (the Desktop behavior); when omitted, the payload is used in place without copying,
|
|
15
|
+
* which suits read-only carriers such as container image layers.
|
|
16
|
+
*/
|
|
17
|
+
readonly root?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Require a named payload source before the Loader activates the tool. */
|
|
20
|
+
export declare const Config: z<Config>;
|
|
21
|
+
/** Canonical build metadata, independent of user-installed packages; legacy files are normalized on read. */
|
|
22
|
+
export interface PrimaryRuntimeManifest {
|
|
23
|
+
readonly desktopVersion: string;
|
|
24
|
+
readonly platform: string;
|
|
25
|
+
readonly arch: string;
|
|
26
|
+
/** Locked payload identity; absent only in installations made before payload hashing. */
|
|
27
|
+
readonly payloadDigest?: string;
|
|
28
|
+
/** Bundled Python interpreter version. */
|
|
29
|
+
readonly python: string;
|
|
30
|
+
/** Absent when the payload ships no Node.js. */
|
|
31
|
+
readonly node?: string;
|
|
32
|
+
/** Bundled pnpm version; requires a bundled Node.js executable. */
|
|
33
|
+
readonly pnpm?: string;
|
|
34
|
+
/** All recorded Python distribution versions; empty for legacy files without a distribution map. */
|
|
35
|
+
readonly pythonPackages: Readonly<Record<string, string>>;
|
|
36
|
+
}
|
|
37
|
+
/** Absolute entry points and bundled versions; pnpm runs through the returned Node executable. */
|
|
38
|
+
export interface WorkspaceDependencies {
|
|
39
|
+
readonly python: string;
|
|
40
|
+
readonly node?: string;
|
|
41
|
+
readonly pnpm?: string;
|
|
42
|
+
readonly pythonPackages: string;
|
|
43
|
+
readonly nodePackages?: string;
|
|
44
|
+
/** Locked distribution versions; excludes packages users add to the installed environment. */
|
|
45
|
+
readonly pythonDistributions: Readonly<Record<string, string>>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Validate payload JSON and normalize legacy component fields to top-level versions.
|
|
49
|
+
* @param value - Untrusted decoded runtime.json contents.
|
|
50
|
+
* @returns Canonical metadata with one Python distribution map and no components field.
|
|
51
|
+
* @throws For malformed or mixed formats, duplicate distribution names, or inconsistent legacy metadata.
|
|
52
|
+
*/
|
|
53
|
+
export declare function parsePrimaryRuntime(value: unknown): PrimaryRuntimeManifest;
|
|
54
|
+
/**
|
|
55
|
+
* Read and normalize build metadata without rewriting the source file.
|
|
56
|
+
* @param root - Installed or bundled primary runtime directory.
|
|
57
|
+
* @returns Validated top-level versions and target identifiers, including for legacy components manifests.
|
|
58
|
+
*/
|
|
59
|
+
export declare function readPrimaryRuntime(root: string): Promise<PrimaryRuntimeManifest>;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve platform-specific interpreter and library locations without changing the environment.
|
|
62
|
+
* @param root - Absolute payload or installation directory.
|
|
63
|
+
* @param manifest - Validated runtime metadata.
|
|
64
|
+
* @returns Absolute paths for explicit script execution and recorded bundled Python versions.
|
|
65
|
+
*/
|
|
66
|
+
export declare function workspaceDependencyPaths(root: string, manifest: PrimaryRuntimeManifest): WorkspaceDependencies;
|
|
67
|
+
/**
|
|
68
|
+
* Use a payload where it lies: validate its metadata and entries, copying nothing.
|
|
69
|
+
* @param source - Payload directory, typically a read-only carrier.
|
|
70
|
+
* @returns Paths into the payload itself.
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolvePrimaryRuntime(source: string): Promise<WorkspaceDependencies>;
|
|
73
|
+
/**
|
|
74
|
+
* Install the application-owned payload locally, retaining a complete previous tree on copy failure.
|
|
75
|
+
* @param source - Payload carried by the current installation.
|
|
76
|
+
* @param root - Fixed primary runtime directory under the Harness home.
|
|
77
|
+
* @returns Paths into the installed payload; no PATH or package-manager configuration is changed.
|
|
78
|
+
*/
|
|
79
|
+
export declare function installPrimaryRuntime(source: string, root: string): Promise<WorkspaceDependencies>;
|
|
80
|
+
/**
|
|
81
|
+
* Register the read-only path query; the first invocation prepares (or merely validates) the payload.
|
|
82
|
+
* @param ctx - Tool registry owner.
|
|
83
|
+
* @param config - Payload location and optional installation directory.
|
|
84
|
+
*/
|
|
85
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
86
|
+
//# sourceMappingURL=index.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-tool-workspace-dependencies",
|
|
3
|
+
"description": "The load_workspace_dependencies tool: absolute paths into a bundled Python, Node.js, and pnpm payload",
|
|
4
|
+
"version": "0.1.7-alpha.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/skill/tool-workspace-dependencies"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"lib/index.js",
|
|
25
|
+
"lib/types/**/*.d.ts"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-alpha.1",
|
|
30
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-alpha.1",
|
|
34
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
35
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.4",
|
|
36
|
+
"@deepseek-ai/cordis-plugin-include": "^1.0.8",
|
|
37
|
+
"@deepseek-ai/dsh-agent": "^0.1.7-alpha.1",
|
|
38
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-alpha.1",
|
|
39
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.7-alpha.1"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@deepseek-ai/schemastery": "^3.18.3"
|
|
43
|
+
}
|
|
44
|
+
}
|