@qilin-agent/tool-workspace-dependencies 0.0.0-stage → 3.1.3

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kkutysllb
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.
@@ -0,0 +1,67 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
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
+ /:
6
+ en: 96f7fe2b814c798f
7
+ zh: 7ee4937b35eba80f
8
+ /qilin-tool-workspace-dependencies:
9
+ en: 9746e3c844d93309
10
+ zh: b1c5d63872311530
11
+ /qilin-tool-workspace-dependencies/summary:
12
+ en: e9ce608fa512599f
13
+ zh: 09fb6924aa46acd5
14
+ /qilin-tool-workspace-dependencies/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: 09388d293f9be9cb
17
+ /qilin-tool-workspace-dependencies/use-this-package:
18
+ en: 40137c2655ff1b8a
19
+ zh: d93c48d438f8a587
20
+ /qilin-tool-workspace-dependencies/use-this-package/minimal-configuration:
21
+ en: e8ed32b430c875c3
22
+ zh: 0646e4e1b9279458
23
+ /qilin-tool-workspace-dependencies/use-this-package/payload-layout:
24
+ en: f92262be81174420
25
+ zh: 9823d4d5b8ad8375
26
+ /qilin-tool-workspace-dependencies/use-this-package/carrier-activation:
27
+ en: 4a97522d95e563f0
28
+ zh: db2107d9d4375750
29
+ /qilin-tool-workspace-dependencies/understand-the-implementation:
30
+ en: 0121e57a754cb26f
31
+ zh: 2c8c7a36fe2b0603
32
+ /qilin-tool-workspace-dependencies/further-exploration:
33
+ en: 5042e4f62017d3e7
34
+ zh: 08c582d257a56a22
35
+ /qilin-tool-workspace-dependencies/model-experience:
36
+ en: 215e7ba838619b7b
37
+ zh: a311da3843709f90
38
+ /qilin-tool-workspace-dependencies/model-experience/tool-schema:
39
+ en: e4c7376d4f29dbd5
40
+ zh: 6b7a9eaf69c9ea53
41
+ /qilin-tool-workspace-dependencies/model-experience/tool-schema/what-the-model-sees:
42
+ en: e58a0dbc3531d942
43
+ zh: 3cadaa61bd65d3ce
44
+ /qilin-tool-workspace-dependencies/model-experience/tool-schema/token-effect:
45
+ en: 615cce9c7fcb746d
46
+ zh: 837f23d49b53151f
47
+ /qilin-tool-workspace-dependencies/model-experience/tool-schema/kv-cache-effect:
48
+ en: 4ee92571a5f8a71a
49
+ zh: b218ce5a939481dc
50
+ /qilin-tool-workspace-dependencies/model-experience/tool-result:
51
+ en: f8ffc2b983f05ffc
52
+ zh: 8573891441963be4
53
+ /qilin-tool-workspace-dependencies/model-experience/tool-result/what-the-model-sees:
54
+ en: c25c9db51e7d06da
55
+ zh: 94fbe03e91eb2382
56
+ /qilin-tool-workspace-dependencies/model-experience/tool-result/token-effect:
57
+ en: 0efada793d1e65b1
58
+ zh: 3eed908f43174b88
59
+ /qilin-tool-workspace-dependencies/model-experience/tool-result/kv-cache-effect:
60
+ en: 42325f307b6c259d
61
+ zh: 658da42a5a6320d4
62
+ /qilin-tool-workspace-dependencies/known-limitations-and-deferred-work:
63
+ en: e8977bc1a21223de
64
+ zh: 9934bad01ad1585c
65
+ /qilin-tool-workspace-dependencies/known-limitations-and-deferred-work/dev-note:
66
+ en: 000d86d9a81c929a
67
+ zh: f1d501916e29e5ed
package/README.md CHANGED
@@ -1,3 +1,147 @@
1
- # Temporary Holding Version
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 QiLin home."
3
+ kind: "package-reference"
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # @qilin-agent/tool-workspace-dependencies
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ Deployments that ship their own script runtimes — a container image layer or a deployment-provided primary runtime — 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 QiLin home on first use 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 (`@qilin-agent/skill-office`) call this tool by name for their default interpreter.
29
+
30
+ ### Minimal configuration
31
+
32
+ ```yaml
33
+ - name: '@qilin-agent/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 QiLin 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
+ ### Carrier activation
48
+
49
+ The `sdk` profile mounts this tool and `@qilin-agent/skill-office` only when a carrier path exists. A deployment declares one of two environment variables; with neither set, both rows stay disabled and no payload is read.
50
+
51
+ | Variable | Meaning |
52
+ |---|---|
53
+ | `QILIN_PRIMARY_RUNTIME` | Absolute path to a `primary-runtime/` payload directory. An empty string opts out even when a packaged carrier default exists. |
54
+ | `QILIN_BUNDLED_PRIMARY_RUNTIME` | Carrier default supplied by a packaged carrier. Its `skill-office` `assetRoot` resolves to the sibling `office-skills/` directory and its Node executable to `<payload>/dependencies/node/bin/node`. |
55
+
56
+ A carrier directory keeps the two resource trees side by side:
57
+
58
+ ```text
59
+ <carrier>/
60
+ primary-runtime/
61
+ runtime.json
62
+ dependencies/python/…
63
+ dependencies/node/…
64
+ dependencies/pnpm/…
65
+ office-skills/
66
+ office-docx/SKILL.md
67
+ office-pptx/SKILL.md
68
+ office-xlsx/SKILL.md
69
+ scripts/check_office.py
70
+ ```
71
+
72
+ The three Office workflows and their shared checker ship in this repository under [`packages/skill/skill-office/assets/`](../skill-office/assets); copy that tree to `<carrier>/office-skills/`. A container can copy both directories into an immutable image layer and set `QILIN_PRIMARY_RUNTIME` to the absolute `primary-runtime/` path; the tool then queries that payload in place. Profile patches can disable `skill-office` or replace its `assetRoot` independently of this tool, and a custom Python-only payload must either patch `skill-office.config.cli: false` or supply `skill-office.config.node`. Missing skill resources produce a startup warning; invalid or incomplete payloads fail the first tool call. Configuration changes require restarting the SDK process.
73
+
74
+ -----
75
+
76
+ <a id="understand-the-implementation"></a>
77
+ ## Understand the implementation
78
+
79
+ <details>
80
+ <summary>Implementation internals — click to expand</summary>
81
+
82
+ `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.
83
+
84
+ | File | Responsibility |
85
+ |---|---|
86
+ | [`src/index.ts`](src/index.ts) | Manifest validation, path derivation, in-place and installed preparation, tool registration. |
87
+
88
+ </details>
89
+
90
+ -----
91
+
92
+ <a id="further-exploration"></a>
93
+ ## Further Exploration
94
+
95
+ - [Office skills](../skill-office/README.md) — the workflows that call this tool for their interpreter.
96
+ - [Tool registry](../../core/tools/README.md) — registration and schemas.
97
+
98
+ -----
99
+
100
+ <a id="model-experience"></a>
101
+ ## Model Experience
102
+
103
+ ### Tool schema
104
+
105
+ #### What the model sees
106
+
107
+ The model sees the generated [`load_workspace_dependencies` schema](../../../docs/tool-catalog.md#qilin-agenttool-workspace-dependencies).
108
+
109
+ #### Token effect
110
+
111
+ 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.
112
+
113
+ #### KV Cache effect
114
+
115
+ Prefix-stable while the tool definition and visibility are unchanged.
116
+
117
+ ### Tool result
118
+
119
+ #### What the model sees
120
+
121
+ 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.
122
+
123
+ #### Token effect
124
+
125
+ A few hundred characters per call; paths dominate.
126
+
127
+ #### KV Cache effect
128
+
129
+ Append-only tool result in the turn history; no prompt section is added.
130
+
131
+ ## Known Limitations and Deferred Work
132
+
133
+ <a id="known-limitations-and-deferred-work"></a>
134
+
135
+ - This repository ships no primary-runtime builder: the payload is always deployment-provided, and the shipped profiles read it only through `QILIN_PRIMARY_RUNTIME`.
136
+ - Linux targets require glibc; musl payloads are not locked.
137
+ - Windows payloads used in place must already be executable from their carrier; the in-place mode performs no permission repair.
138
+
139
+ <a id="dev-note"></a>
140
+ ### Dev Note
141
+
142
+ <details>
143
+ <summary>Working context for maintainers — click to expand</summary>
144
+
145
+ The carrier boundary is recorded in the [desktop and account alignment boundaries](../../../.agents/notes/implemented/architecture/2026-09-25-desktop-and-account-alignment-boundaries.md): this repository neither builds nor releases the desktop carrier, so the payload directory arrives from the deployment.
146
+
147
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,147 @@
1
+ ---
2
+ description: "load_workspace_dependencies 工具:返回随包 Python、Node.js 与 pnpm payload 的绝对路径,可原位使用或安装到 QiLin home 下。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @qilin-agent/tool-workspace-dependencies
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ 自带脚本运行时的部署——容器镜像层或部署方提供的 primary runtime——挂载本工具后,agent 可以直接询问随包 Python、Node.js 与 pnpm 的位置,而无需探测系统解释器。工具返回绝对路径与记录的发行版版本,既不修改 `PATH`,也不修改包管理器设置。payload 可以首次使用时复制到 QiLin home 下,也可以在原地使用(只读载体)。
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(`@qilin-agent/skill-office`)按名称调用本工具以取得默认解释器。
29
+
30
+ ### 最小配置
31
+
32
+ ```yaml
33
+ - name: '@qilin-agent/tool-workspace-dependencies'
34
+ config:
35
+ source: /path/to/primary-runtime
36
+ ```
37
+
38
+ | 字段 | 默认值 | 含义 |
39
+ |---|---|---|
40
+ | `source` | 必填 | 携带 `runtime.json` 与 `dependencies/` 的绝对 payload 目录。 |
41
+ | `root` | 未设置 | QiLin home 下的绝对安装目录。设置后:首次调用时把 payload 复制到该处,并在 `runtime.json` 未变化时复用。未设置:校验并按原位使用 payload,不复制任何内容。 |
42
+
43
+ ### payload 布局
44
+
45
+ `runtime.json` 记录 `desktopVersion`、`platform`(`win32`、`darwin` 或 `linux`)、`arch`、可选的 `payloadDigest`、顶层 `python`、可选的 `node`/`pnpm` 版本,以及完整的 `pythonPackages` 发行版版本映射。声明 pnpm 就必须声明 Node.js。Python 库(包括 numpy 与 pandas)只出现在 `pythonPackages` 中。具体条目位于 `dependencies/`:`python/bin/python3`(Windows 为 `python/python.exe`)及其下的 `site-packages`;声明时还有 `node/bin/node` 及 `node/node_modules` 和 `pnpm/bin/pnpm.mjs`。平台或架构与当前进程不一致的清单会被拒绝。
46
+
47
+ ### 载体启用
48
+
49
+ `sdk` profile 仅在存在载体路径时挂载本工具与 `@qilin-agent/skill-office`。部署方声明下面两个环境变量之一;两者都未设置时,两行保持禁用,也不会读取任何 payload。
50
+
51
+ | 环境变量 | 含义 |
52
+ |---|---|
53
+ | `QILIN_PRIMARY_RUNTIME` | `primary-runtime/` payload 目录的绝对路径。即使存在随包载体默认路径,空字符串也表示禁用。 |
54
+ | `QILIN_BUNDLED_PRIMARY_RUNTIME` | 由打包载体提供的默认路径。其 `skill-office` 的 `assetRoot` 解析为同级的 `office-skills/` 目录,其 Node 可执行文件解析为 `<payload>/dependencies/node/bin/node`。 |
55
+
56
+ 载体目录把两棵资源树并排放置:
57
+
58
+ ```text
59
+ <carrier>/
60
+ primary-runtime/
61
+ runtime.json
62
+ dependencies/python/…
63
+ dependencies/node/…
64
+ dependencies/pnpm/…
65
+ office-skills/
66
+ office-docx/SKILL.md
67
+ office-pptx/SKILL.md
68
+ office-xlsx/SKILL.md
69
+ scripts/check_office.py
70
+ ```
71
+
72
+ 三套 Office 工作流及其共享检查器随本仓提供,位于 [`packages/skill/skill-office/assets/`](../skill-office/assets);把该目录树复制到 `<carrier>/office-skills/` 即可。容器可以把两个目录一起复制进不可变镜像层,并把 `QILIN_PRIMARY_RUNTIME` 设为 `primary-runtime/` 的绝对路径;此时工具原位查询该 payload。profile patch 可以独立于本工具禁用 `skill-office` 或替换其 `assetRoot`;自定义的纯 Python payload 必须把 `skill-office.config.cli` patch 为 `false`,或提供 `skill-office.config.node`。缺少 skill 资源会产生启动警告;无效或不完整的 payload 会在首次工具调用时失败。配置变更需要重启 SDK 进程。
73
+
74
+ -----
75
+
76
+ <a id="understand-the-implementation"></a>
77
+ ## 理解实现
78
+
79
+ <details>
80
+ <summary>实现内部细节——点击展开</summary>
81
+
82
+ `readPrimaryRuntime` 与构建期冒烟检查共用 `parsePrimaryRuntime`。该校验函数校验扁平清单,并拒绝归一化后重名的发行版。旧式 `components` 元数据在内存中归一化,保留其一致性检查;缺失的旧式发行版映射变为空映射。同时出现扁平与旧式版本字段会被拒绝。读取不重写元数据,归一化后等价的清单可以复用已安装的 payload。`workspaceDependencyPaths` 推导各平台的条目。`installPrimaryRuntime` 先复制到暂存目录,要求声明的解释器与脚本是文件、包根是目录,然后换入到位,并在失败时保留原有目录树;`resolvePrimaryRuntime` 校验同样的条目但不复制。工具在插件生命周期内记忆首次成功的准备结果。
83
+
84
+ | 文件 | 职责 |
85
+ |---|---|
86
+ | [`src/index.ts`](src/index.ts) | 清单校验、路径推导、原位与安装式准备、工具注册。 |
87
+
88
+ </details>
89
+
90
+ -----
91
+
92
+ <a id="further-exploration"></a>
93
+ ## 进一步探索
94
+
95
+ - [Office skills](../skill-office/README.zh.md)——调用本工具取得解释器的工作流。
96
+ - [工具注册表](../../core/tools/README.zh.md)——注册与 schema。
97
+
98
+ -----
99
+
100
+ <a id="model-experience"></a>
101
+ ## 模型体验
102
+
103
+ ### 工具 schema
104
+
105
+ #### 模型看到什么
106
+
107
+ 模型看到生成的 [`load_workspace_dependencies` schema](../../../docs/tool-catalog.zh.md#qilin-agenttool-workspace-dependencies)。
108
+
109
+ #### Token 影响
110
+
111
+ 在工具可见处每次请求的固定 schema 成本;描述中列出了随包的 Office 库,因此模型无需先加载 skill 即可选择解释器。
112
+
113
+ #### KV Cache 影响
114
+
115
+ 只要工具定义与可见性不变,前缀保持稳定。
116
+
117
+ ### 工具结果
118
+
119
+ #### 模型看到什么
120
+
121
+ 一个 JSON 对象,包含绝对的 `python` 与 `pythonPackages` 路径、来自 `runtime.json` 的 `pythonDistributions`,以及 payload 声明时的 `node`、`nodePackages` 和 `pnpm`。重复调用返回同一个对象。
122
+
123
+ #### Token 影响
124
+
125
+ 每次调用几百个字符,以路径为主。
126
+
127
+ #### KV Cache 影响
128
+
129
+ 工具结果只追加到本轮历史;不新增提示词区段。
130
+
131
+ ## 已知限制与延期工作
132
+
133
+ <a id="known-limitations-and-deferred-work"></a>
134
+
135
+ - 本仓不提供 primary-runtime 构建器:payload 始终由部署方提供,随附 profile 只通过 `QILIN_PRIMARY_RUNTIME` 读取它。
136
+ - Linux 目标要求 glibc;musl payload 未纳入锁定。
137
+ - 原位使用的 Windows payload 必须已在载体中可执行;原位模式不修复权限。
138
+
139
+ <a id="dev-note"></a>
140
+ ### 开发备注
141
+
142
+ <details>
143
+ <summary>维护者的工作上下文——点击展开</summary>
144
+
145
+ 载体边界记录在[桌面与账号对齐边界](../../../.agents/notes/implemented/architecture/2026-09-25-desktop-and-account-alignment-boundaries.zh.md)中:本仓既不构建也不发布桌面载体,因此 payload 目录由部署方提供。
146
+
147
+ </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 "@qilin-agent/schemastery";
4
+ import { defineTool } from "@qilin-agent/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 QiLin 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 QiLin 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 QiLin home. */
2
+ import type { Context } from '@qilin-agent/kylin';
3
+ import z from '@qilin-agent/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 QiLin home. When set, the payload is copied there on the
14
+ * first call (the copy-on-first-use 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 QiLin 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 CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@qilin-agent/tool-workspace-dependencies",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "The load_workspace_dependencies tool: absolute paths into a bundled Python, Node.js, and pnpm payload",
4
+ "version": "3.1.3",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kkutysllb/QiLin.git",
11
+ "directory": "packages/skill/tool-workspace-dependencies"
12
+ },
13
+ "engines": {
14
+ "node": "^22.19.0 || >=24.0.0"
15
+ },
16
+ "type": "module",
17
+ "main": "lib/index.js",
18
+ "types": "lib/types/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./lib/types/index.d.ts",
22
+ "default": "./lib/index.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "files": [
27
+ "lib/index.js",
28
+ "lib/types/**/*.d.ts"
29
+ ],
30
+ "license": "MIT",
31
+ "peerDependencies": {
32
+ "@qilin-agent/tools": "3.1.3",
33
+ "@qilin-agent/kylin": "~4.0.3"
34
+ },
35
+ "devDependencies": {
36
+ "@qilin-agent/agent": "3.1.3",
37
+ "@qilin-agent/llm": "3.1.3",
38
+ "@qilin-agent/system-prompt": "3.1.3",
39
+ "@qilin-agent/tools": "3.1.3",
40
+ "@qilin-agent/kylin": "~4.0.3",
41
+ "@qilin-agent/kylin-plugin-include": "~1.0.8",
42
+ "@qilin-agent/kylin-plugin-loader": "~1.0.4"
43
+ },
44
+ "dependencies": {
45
+ "@qilin-agent/schemastery": "~3.18.3"
46
+ }
6
47
  }