@jianxx/dsh-cc-tool-search 0.1.0

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,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -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/core/tool-search/README.md
5
+ README.md: 3d64b50c6a37ccb683d786ecba7618f888b9015e
6
+ README.zh.md: d347ab0bedf02e7c16a9cd8e98f949263448e550
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # dsh-tool-search
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ Deferred tool registration plus a model-facing **ToolSearch** tool that loads heavy tools on demand. A tool that is expensive to load or token-heavy in the prompt can be registered **deferred**: it contributes a name, description, and search hint to a searchable registry, but its real definition stays **out of the model-visible schema and prompt** until the model asks for it.
6
+
7
+ ## Why defer tools
8
+
9
+ Every visible tool schema costs prompt tokens and, for a heavy capability, load time. Most sessions need only a handful of tools from a large catalog. Deferral carries the *capability metadata* cheaply and loads the material definition only when the model actually summons it — generalizing the `shouldDefer`/ToolSearch mechanism (and the MCP `_meta['anthropic/alwaysLoad']` escape hatch) into the harness's effect-based registry model.
10
+
11
+ ## What it does
12
+
13
+ [`DeferredToolRegistry`](src/index.ts) is the `ctx.toolSearch` service. A plugin registers a capability deferred:
14
+
15
+ ```ts
16
+ ctx.toolSearch.registerDeferred({
17
+ name: 'big_fs_tool',
18
+ description: 'Heavy filesystem capability invoked once per session.',
19
+ searchHint: 'read write edit files',
20
+ alwaysLoad: false,
21
+ activate: () => ctx.tools.register(bigFsToolDefinition), // returns the exact disposer
22
+ })
23
+ ```
24
+
25
+ The **ToolSearch** model tool then ranks the deferred set against a `query` (`max_results`, default 5) using weighted substring/token matching over name, `searchHint`, and description. On a hit it calls the registration's `activate()` callback — a real `ctx.tools.register()` — so the tool becomes visible to the **next** assembly and then callable exactly like any top-of-prompt tool. `alwaysLoad: true` registers immediately at defer time and is never a deferred candidate.
26
+
27
+ The design rides the repo's effect conventions:
28
+
29
+ - **Deferred registration is an effect.** The returned disposer reclaims the deferred entry *and* any activated `ctx.tools` registration together, so a plugin unloading mid-session tears down both.
30
+ - **Activation is idempotent.** Re-activating an already-loaded tool is a no-op; re-registration (which would throw on a duplicate name) never happens.
31
+ - **Restriction stays authoritative.** `registerDeferred` reserves the tool's name in the `dsh-tools` registry (known but not visible), so a scoped `restrict()` can deny it *before* it loads. ToolSearch checks that gate: a deferred tool a scope denies is **not** activated for that scope, and the result says why.
32
+ - **Search is scope-visible.** A deferred registration lands in the calling scope's layer, and ToolSearch searches the *calling agent's* scope chain (itself and ancestors) — so a tool an agent deferred is visible to that agent's search and its descendants, but a sibling scope or the global view never sees it. A globally-deferred tool remains globally visible as before.
33
+ - **Deferred names never leak into the prompt.** Only activated (`alwaysLoad` or loaded) tools enter the schema; reserved-but-unloaded names are known for restriction/`toolOrder` purposes but never model-visible.
34
+
35
+ ## Model Experience
36
+
37
+ Turn one, the model sees only `ToolSearch` plus whatever is already loaded (including `alwaysLoad` tools). It searches for a capability by keywords, and the result names the now-available tools. On the next turn those tools are first-class: declare them, call them. Already-loaded tools drop out of the deferred set, so a second search for them is correctly empty.
38
+
39
+ ## Known Limitations and Deferred Work
40
+
41
+ - **Matching is lexical, not semantic.** Ranking uses weighted substring/token scoring; there is no embedding/vector search. This keeps the seam dependency-free and deterministic, at the cost of synonyms that share no token.
42
+ - **Global activation.** Loading happens on the host plane (global registration); a per-agent scope sees a loaded tool only if its restriction admits it. There is no per-conversation discovery history — loading is an in-memory effect, so a re-registered composition starts from the deferred pool again.
package/README.zh.md ADDED
@@ -0,0 +1,41 @@
1
+ # dsh-tool-search
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 延迟工具注册,外加一个模型可见的 **ToolSearch** 工具,按需加载重工具。一个加载成本高、或提示词里占用 token 很多的工具,可以**延迟**注册:它只贡献名字、描述和搜索提示到这个可搜索的注册表,真正的定义在模型主动索要之前始终**不进模型可见的 schema 与提示词**。
6
+
7
+ ## 为什么要延迟工具
8
+
9
+ 每一个可见的工具 schema 都要花提示词 token,重工具还要花加载时间。大多数会话只需要一个大目录里的一小部分工具。延迟机制以很低的成本携带*能力元数据*,只有当模型真正召唤时才加载实体定义——把 `shouldDefer`/ToolSearch 机制(以及 MCP 的 `_meta['anthropic/alwaysLoad']` 逃生舱)泛化进本仓基于 effect 的注册表模型。
10
+
11
+ ## 它做什么
12
+
13
+ [`DeferredToolRegistry`](src/index.ts) 就是 `ctx.toolSearch` 服务。插件可以这样延迟注册一个能力:
14
+
15
+ ```ts
16
+ ctx.toolSearch.registerDeferred({
17
+ name: 'big_fs_tool',
18
+ description: 'Heavy filesystem capability invoked once per session.',
19
+ searchHint: 'read write edit files',
20
+ alwaysLoad: false,
21
+ activate: () => ctx.tools.register(bigFsToolDefinition), // returns the exact disposer
22
+ })
23
+ ```
24
+
25
+ **ToolSearch** 模型工具随后针对 `query`(`max_results`,默认 5)对延迟集合做排序——在名字、`searchHint`、描述上做加权的子串/词元匹配。命中后就调用该注册的 `activate()` 回调——一次真实的 `ctx.tools.register()`——于是该工具在**下一轮**组装中变得可见,此后与任何提示词顶部的工具无异地被调用。`alwaysLoad: true` 在延迟之时立即注册,且永远不会成为延迟候选。
26
+
27
+ 这套设计贴合本仓的 effect 约定:
28
+
29
+ - **延迟注册本身就是 effect。** 返回的 disposer 会连同回收延迟条目*以及*任何已激活的 `ctx.tools` 注册,因此插件在会话中途卸载时,两者一起被拆除。
30
+ - **激活是幂等的。** 再次激活一个已加载的工具是空操作;重复注册(否则会因重名而抛错)绝不会发生。
31
+ - **限制仍然优先。** `registerDeferred` 会在 `dsh-tools` 注册表里预留这个工具名(已知但不可见),因此局部的 `restrict()` 可以在它*加载前*将其拒绝。ToolSearch 会检查这道闸:被某个作用域拒绝的延迟工具,绝不会为该作用域激活,结果里会说明原因。
32
+ - **延迟名字永不泄漏进提示词。** 只有已激活(`alwaysLoad` 或已加载)的工具才进 schema;被预留而未加载的名字仅用于限制/`toolOrder` 判定,不对外可见。
33
+
34
+ ## 模型体验
35
+
36
+ 第一轮,模型只看到 `ToolSearch` 加已加载的(包括 `alwaysLoad` 的)工具。它按关键字搜索某个能力,结果里点名现在可用哪些工具。下一轮这些工具就成头等公民:声明它们,调用它们。已加载的工具会退出延迟集合,因此再次搜索它们会正确地得到空结果。
37
+
38
+ ## 已知限制与暂缓事项
39
+
40
+ - **匹配是词法而非语义的。** 排序用加权的子串/词元打分;没有嵌入/向量检索。这让接缝保持零依赖且确定,代价是无法命中不共享词元的同义词。
41
+ - **全局激活。** 加载发生在宿主平面(全局注册);某个 per-agent 作用域只有在它的限制放行时才能看到已加载的工具。没有按会话的记忆历史——加载是内存里的 effect,因此重新组装后又会从延迟池重新开始。
package/lib/index.d.ts ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Deferred tool registration and the model-facing ToolSearch tool that loads
3
+ * heavy tools on demand.
4
+ *
5
+ * A tool that is expensive to load or token-heavy in the prompt can register
6
+ * DEFERRED: it contributes name + description + search hint to a registry the
7
+ * model can search, but its real definition stays out of the model-visible
8
+ * schema and prompt until the model asks for it. The ToolSearch tool ranks the
9
+ * deferred set against a query and, on a hit, runs the deferred registration's
10
+ * activation callback — which performs the real `ctx.tools.register()`. Because
11
+ * registration is an effect, the tool becomes visible to the NEXT assembly and
12
+ * lives exactly as long as its owning plugin fiber.
13
+ *
14
+ * The design generalizes the `shouldDefer`/ToolSearch mechanism (and the MCP
15
+ * `anthropic/alwaysLoad` escape hatch) into the harness's effect-based registry
16
+ * model: {@link DeferredToolRegistry.registerDeferred} is itself an effect, and
17
+ * {@link registry.activate} gate the load behind the calling scope's tool
18
+ * restriction, so a deferred tool a scope denies is never loaded for it.
19
+ * @module @jianxx/dsh-cc-tool-search
20
+ */
21
+ import { Context, Service } from '@deepseek-ai/cordis';
22
+ import type { ScopeKey } from '@deepseek-ai/dsh-scope';
23
+ /** The model-facing ToolSearch tool name. */
24
+ export declare const TOOL_SEARCH_NAME = "ToolSearch";
25
+ /** A deferred registration: capability metadata before its heavy definition loads. */
26
+ export interface DeferredToolRegistration {
27
+ /** The eventual registered tool name, unique within one deferred registry layer. */
28
+ readonly name: string;
29
+ /** The model-facing description, shown in the ToolSearch result and later the tool schema. */
30
+ readonly description: string;
31
+ /** A short capability phrase for keyword matching; prefer terms not in the name. */
32
+ readonly searchHint?: string;
33
+ /**
34
+ * Escape hatch that never defers: `true` activates the tool immediately at
35
+ * registration, generalizing MCP's `_meta['anthropic/alwaysLoad']`. An
36
+ * `alwaysLoad` tool is registered and model-visible from the start and is
37
+ * never a ToolSearch candidate.
38
+ */
39
+ readonly alwaysLoad?: boolean;
40
+ /**
41
+ * Perform the real registration. Called at most once (the first admitting
42
+ * ToolSearch hit, or immediately for an {@link DeferredToolRegistration.alwaysLoad}
43
+ * tool). Must register a definition on `ctx.tools` and return that exact
44
+ * disposer, so the tool unloads with the deferred entry.
45
+ */
46
+ readonly activate: () => () => void;
47
+ }
48
+ /** One ranked deferred-tool match returned by a ToolSearch query. */
49
+ export interface DeferredSearchHit {
50
+ /** The deferred tool name. */
51
+ readonly name: string;
52
+ /** The deferred tool description. */
53
+ readonly description: string;
54
+ /** The deferred tool's optional search hint. */
55
+ readonly searchHint?: string;
56
+ }
57
+ /** Result of attempting to load one deferred tool for a scope. */
58
+ export type DeferredActivationResult = {
59
+ readonly status: 'loaded';
60
+ readonly name: string;
61
+ } | {
62
+ readonly status: 'already-loaded';
63
+ readonly name: string;
64
+ } | {
65
+ readonly status: 'denied';
66
+ readonly name: string;
67
+ readonly reason: string;
68
+ } | {
69
+ readonly status: 'unknown';
70
+ readonly name: string;
71
+ };
72
+ /**
73
+ * Deferred tool registry and the ToolSearch tool.
74
+ *
75
+ * Deferred registrations are host-plane effects: they land in the calling
76
+ * context's scope layer and unwind with it. The tool itself (registered on the
77
+ * `tools` row) lets a model search the deferred set and load a hit — gated by
78
+ * the calling agent's tool restriction, so a deferred tool a scope denies is
79
+ * never loaded for it.
80
+ */
81
+ export declare class DeferredToolRegistry extends Service {
82
+ static inject: string[];
83
+ private readonly layers;
84
+ constructor(ctx: Context);
85
+ /**
86
+ * Register a deferred capability. The name+description (+hint) are searchable
87
+ * but invisible until activated; an {@link DeferredToolRegistration.alwaysLoad}
88
+ * tool activates immediately for everyone. Registration is an effect: the
89
+ * returned disposer removes the deferred entry and, if it was activated, its
90
+ * real `ctx.tools` registration together.
91
+ * @param reg - the deferred descriptor and activation callback.
92
+ * @returns the exact disposer that reclaims both the deferred entry and any loaded tool.
93
+ */
94
+ registerDeferred(reg: DeferredToolRegistration): () => void;
95
+ /**
96
+ * Rank the deferred, not-yet-loaded tools against a query, searching the scope
97
+ * chain the caller lives in. Returns the top `maxResults` accessible
98
+ * candidates in descending relevance. This is the pure matching step; loading
99
+ * (and its restriction gate) happens in {@link DeferredToolRegistry.activate}.
100
+ * @param query - free-text keyword query.
101
+ * @param maxResults - how many hits to return (default 5).
102
+ * @param scope - the calling scope whose deferred set (and its ancestors') is
103
+ * searched; defaults to this registry's own scope ([`scopeOf`]) on the
104
+ * service context, which is global for a global registry.
105
+ * @returns ranked, scored matches that are still deferred.
106
+ */
107
+ search(query: string, maxResults?: number, scope?: ScopeKey): DeferredSearchHit[];
108
+ /**
109
+ * Load one deferred tool for a scope, idempotently. A hit that the scope's
110
+ * tool restriction denies is NOT loaded (guard semantics take priority over
111
+ * ToolSearch, so the loading gate is not an end-run around `restrict()`); an
112
+ * already-loaded or `alwaysLoad` tool reports `already-loaded` without
113
+ * re-registering; an absent name reports `unknown`.
114
+ * @param name - the deferred tool name.
115
+ * @param scope - the calling agent scope whose restriction gates the load.
116
+ * @returns the load outcome for the model-facing result.
117
+ */
118
+ activate(name: string, scope?: ScopeKey): DeferredActivationResult;
119
+ /**
120
+ * Every deferred entry in scope order (global then the scope chain nearest
121
+ * last) for the calling scope. A caller with no explicit scope searches the
122
+ * registry's own scope: a global registry resolves to the global layer
123
+ * (`scopeOf(this.ctx)` is `undefined` on an unscoped service context), while
124
+ * a registry hosted under a scoped preset backs a child scope so its deferred
125
+ * set — and its ancestors' — stays visible to it.
126
+ */
127
+ private allDeferred;
128
+ /** Run the entry's activation callback once and retain its disposer. */
129
+ private activateStore;
130
+ /** Tear down an activated tool's real registration, if any. */
131
+ private deactivateStore;
132
+ /** The model-facing ToolSearch tool, registered once at construction. */
133
+ private toolDefinition;
134
+ }
135
+ declare module '@deepseek-ai/cordis' {
136
+ interface Context {
137
+ toolSearch: DeferredToolRegistry;
138
+ }
139
+ }
140
+ export default DeferredToolRegistry;
141
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,wBAAwB,CAAA;AAMlE,6CAA6C;AAC7C,eAAO,MAAM,gBAAgB,eAAe,CAAA;AAE5C,sFAAsF;AACtF,MAAM,WAAW,wBAAwB;IACvC,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,8FAA8F;IAC9F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,oFAAoF;IACpF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,MAAM,IAAI,CAAA;CACpC;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,qCAAqC;IACrC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,gDAAgD;IAChD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,kEAAkE;AAClE,MAAM,MAAM,wBAAwB,GAChC;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7E;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AA2DzD;;;;;;;;GAQG;AACH,qBAAa,oBAAqB,SAAQ,OAAO;IAC/C,MAAM,CAAC,MAAM,WAAY;IAEzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAKtB;gBAEW,GAAG,EAAE,OAAO;IAOxB;;;;;;;;OAQG;IACH,gBAAgB,CAAC,GAAG,EAAE,wBAAwB,GAAG,MAAM,IAAI;IA4B3D;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,MAAU,EAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,iBAAiB,EAAE;IAepF;;;;;;;;;OASG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,wBAAwB;IAelE;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IAKnB,wEAAwE;IACxE,OAAO,CAAC,aAAa;IAQrB,+DAA+D;IAC/D,OAAO,CAAC,eAAe;IAQvB,yEAAyE;IACzE,OAAO,CAAC,cAAc;CAyEvB;AAqBD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,UAAU,EAAE,oBAAoB,CAAA;KACjC;CACF;AAED,eAAe,oBAAoB,CAAA"}
package/lib/index.js ADDED
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Deferred tool registration and the model-facing ToolSearch tool that loads
3
+ * heavy tools on demand.
4
+ *
5
+ * A tool that is expensive to load or token-heavy in the prompt can register
6
+ * DEFERRED: it contributes name + description + search hint to a registry the
7
+ * model can search, but its real definition stays out of the model-visible
8
+ * schema and prompt until the model asks for it. The ToolSearch tool ranks the
9
+ * deferred set against a query and, on a hit, runs the deferred registration's
10
+ * activation callback — which performs the real `ctx.tools.register()`. Because
11
+ * registration is an effect, the tool becomes visible to the NEXT assembly and
12
+ * lives exactly as long as its owning plugin fiber.
13
+ *
14
+ * The design generalizes the `shouldDefer`/ToolSearch mechanism (and the MCP
15
+ * `anthropic/alwaysLoad` escape hatch) into the harness's effect-based registry
16
+ * model: {@link DeferredToolRegistry.registerDeferred} is itself an effect, and
17
+ * {@link registry.activate} gate the load behind the calling scope's tool
18
+ * restriction, so a deferred tool a scope denies is never loaded for it.
19
+ * @module @jianxx/dsh-cc-tool-search
20
+ */
21
+ import { Service } from '@deepseek-ai/cordis';
22
+ import { NamedEntries, ScopedLayers, scopeOf } from '@deepseek-ai/dsh-scope';
23
+ import { defineTool } from '@jianxx/dsh-cc-tools';
24
+ /** The model-facing ToolSearch tool name. */
25
+ export const TOOL_SEARCH_NAME = 'ToolSearch';
26
+ /** One scope's aggregate deferred-tool registration. */
27
+ class DeferredLayer {
28
+ tools = new NamedEntries(name => new Error(`tool "${name}" is already registered as deferred in this scope`));
29
+ isEmpty() {
30
+ return this.tools.isEmpty();
31
+ }
32
+ }
33
+ /**
34
+ * How much a deferred tool matches a query, summed over query tokens and the
35
+ * weighted name/description/searchHint fields. `name` carries the most weight,
36
+ * `searchHint` next, then `description`; an exact name match is conclusive.
37
+ * Pure and deterministic: used only to rank the candidate set.
38
+ * @param stored - the deferred registration.
39
+ * @param query - the raw ToolSearch query.
40
+ * @returns a non-negative score; `0` means no match.
41
+ */
42
+ function scoreStored(stored, query) {
43
+ const q = query.toLowerCase().trim();
44
+ if (q.length === 0)
45
+ return 0;
46
+ const qTokens = tokenize(q);
47
+ const fields = [
48
+ [stored.reg.name.toLowerCase(), 10],
49
+ [(stored.reg.searchHint ?? '').toLowerCase(), 4],
50
+ [stored.reg.description.toLowerCase(), 2],
51
+ ];
52
+ let total = 0;
53
+ for (const [text, weight] of fields) {
54
+ if (text.length === 0)
55
+ continue;
56
+ for (const token of qTokens) {
57
+ if (token.length === 0)
58
+ continue;
59
+ if (text.includes(token)) {
60
+ total += weight;
61
+ }
62
+ else if (tokenize(text).some(word => word.startsWith(token) || token.startsWith(word))) {
63
+ total += Math.max(1, Math.floor(weight / 2));
64
+ }
65
+ }
66
+ }
67
+ return total;
68
+ }
69
+ /** Lowercase the input and split into alphanumeric tokens, dropping empty ones. */
70
+ function tokenize(value) {
71
+ return value.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
72
+ }
73
+ /**
74
+ * Deferred tool registry and the ToolSearch tool.
75
+ *
76
+ * Deferred registrations are host-plane effects: they land in the calling
77
+ * context's scope layer and unwind with it. The tool itself (registered on the
78
+ * `tools` row) lets a model search the deferred set and load a hit — gated by
79
+ * the calling agent's tool restriction, so a deferred tool a scope denies is
80
+ * never loaded for it.
81
+ */
82
+ export class DeferredToolRegistry extends Service {
83
+ static inject = ['tools'];
84
+ layers = new ScopedLayers(() => new DeferredLayer(),
85
+ // Deferred-set changes are not model-visible (deferred tools never enter
86
+ // the prompt); actual activations fire `tools/change` through `register()`.
87
+ () => { });
88
+ constructor(ctx) {
89
+ super(ctx, 'toolSearch');
90
+ // The ToolSearch tool itself is never deferred: it is the load path, so it
91
+ // must be visible on the first assembly.
92
+ ctx.tools.register(this.toolDefinition());
93
+ }
94
+ /**
95
+ * Register a deferred capability. The name+description (+hint) are searchable
96
+ * but invisible until activated; an {@link DeferredToolRegistration.alwaysLoad}
97
+ * tool activates immediately for everyone. Registration is an effect: the
98
+ * returned disposer removes the deferred entry and, if it was activated, its
99
+ * real `ctx.tools` registration together.
100
+ * @param reg - the deferred descriptor and activation callback.
101
+ * @returns the exact disposer that reclaims both the deferred entry and any loaded tool.
102
+ */
103
+ registerDeferred(reg) {
104
+ const stored = {
105
+ reg,
106
+ alwaysLoad: reg.alwaysLoad === true,
107
+ activated: false,
108
+ disposer: undefined,
109
+ };
110
+ const unwind = this.layers.effect(this.ctx, (layer) => {
111
+ const undo = layer.tools.insert(reg.name, stored);
112
+ if (stored.alwaysLoad)
113
+ this.activateStore(stored);
114
+ return () => {
115
+ undo();
116
+ this.deactivateStore(stored);
117
+ };
118
+ }, { label: 'toolSearch.registerDeferred()' });
119
+ // Reserve the name in the tools registry so a scoped restriction can gate
120
+ // it before its definition loads (restrict only names known capabilities).
121
+ const reserve = this.ctx.tools.reserve(reg.name);
122
+ return () => {
123
+ unwind();
124
+ reserve();
125
+ };
126
+ }
127
+ /**
128
+ * Rank the deferred, not-yet-loaded tools against a query, searching the scope
129
+ * chain the caller lives in. Returns the top `maxResults` accessible
130
+ * candidates in descending relevance. This is the pure matching step; loading
131
+ * (and its restriction gate) happens in {@link DeferredToolRegistry.activate}.
132
+ * @param query - free-text keyword query.
133
+ * @param maxResults - how many hits to return (default 5).
134
+ * @param scope - the calling scope whose deferred set (and its ancestors') is
135
+ * searched; defaults to this registry's own scope ([`scopeOf`]) on the
136
+ * service context, which is global for a global registry.
137
+ * @returns ranked, scored matches that are still deferred.
138
+ */
139
+ search(query, maxResults = 5, scope) {
140
+ const candidates = [];
141
+ for (const stored of this.allDeferred(scope)) {
142
+ if (stored.alwaysLoad || stored.activated)
143
+ continue;
144
+ const score = scoreStored(stored, query);
145
+ if (score > 0)
146
+ candidates.push({ stored, score });
147
+ }
148
+ candidates.sort((a, b) => b.score - a.score || a.stored.reg.name.localeCompare(b.stored.reg.name));
149
+ return candidates.slice(0, maxResults).map(({ stored }) => ({
150
+ name: stored.reg.name,
151
+ description: stored.reg.description,
152
+ ...stored.reg.searchHint !== undefined ? { searchHint: stored.reg.searchHint } : {},
153
+ }));
154
+ }
155
+ /**
156
+ * Load one deferred tool for a scope, idempotently. A hit that the scope's
157
+ * tool restriction denies is NOT loaded (guard semantics take priority over
158
+ * ToolSearch, so the loading gate is not an end-run around `restrict()`); an
159
+ * already-loaded or `alwaysLoad` tool reports `already-loaded` without
160
+ * re-registering; an absent name reports `unknown`.
161
+ * @param name - the deferred tool name.
162
+ * @param scope - the calling agent scope whose restriction gates the load.
163
+ * @returns the load outcome for the model-facing result.
164
+ */
165
+ activate(name, scope) {
166
+ const stored = this.allDeferred(scope).find(candidate => candidate.reg.name === name);
167
+ if (stored === undefined) {
168
+ return { status: 'unknown', name };
169
+ }
170
+ if (stored.alwaysLoad || stored.activated) {
171
+ return { status: 'already-loaded', name };
172
+ }
173
+ if (!this.ctx.tools.isAdmitted(name, scope)) {
174
+ return { status: 'denied', name, reason: `"${name}" is restricted for this agent; loading it would be refused` };
175
+ }
176
+ this.activateStore(stored);
177
+ return { status: 'loaded', name };
178
+ }
179
+ /**
180
+ * Every deferred entry in scope order (global then the scope chain nearest
181
+ * last) for the calling scope. A caller with no explicit scope searches the
182
+ * registry's own scope: a global registry resolves to the global layer
183
+ * (`scopeOf(this.ctx)` is `undefined` on an unscoped service context), while
184
+ * a registry hosted under a scoped preset backs a child scope so its deferred
185
+ * set — and its ancestors' — stays visible to it.
186
+ */
187
+ allDeferred(scope) {
188
+ const key = scope ?? scopeOf(this.ctx) ?? undefined;
189
+ return [...this.layers.merge(key, layer => layer.tools).values()];
190
+ }
191
+ /** Run the entry's activation callback once and retain its disposer. */
192
+ activateStore(stored) {
193
+ if (stored.activated)
194
+ return;
195
+ // The callback performs `ctx.tools.register(...)` and returns that exact
196
+ // disposer, so unloading the deferred entry also unregisters the tool.
197
+ stored.disposer = stored.reg.activate();
198
+ stored.activated = true;
199
+ }
200
+ /** Tear down an activated tool's real registration, if any. */
201
+ deactivateStore(stored) {
202
+ if (stored.activated) {
203
+ stored.disposer?.();
204
+ stored.disposer = undefined;
205
+ stored.activated = false;
206
+ }
207
+ }
208
+ /** The model-facing ToolSearch tool, registered once at construction. */
209
+ toolDefinition() {
210
+ return defineTool({
211
+ name: TOOL_SEARCH_NAME,
212
+ description: 'Search for deferred tools that are NOT currently loaded, then load the ones you need by their name. '
213
+ + 'Deferred tools are capabilities (e.g. filesystem, shell, or web tools) omitted from your function list to save prompt '
214
+ + 'space. Search with the capability you need; loading a tool makes it available to call exactly like any tool defined at '
215
+ + 'the top of the prompt. Loading is idempotent: loading an already-available tool is a harmless no-op.',
216
+ parameters: {
217
+ query: {
218
+ type: 'string',
219
+ required: true,
220
+ description: 'The capability or tool to find, as keywords (e.g. "read file", "edit", "bash").',
221
+ },
222
+ max_results: {
223
+ type: 'number',
224
+ description: 'How many matching deferred tools to evaluate (default 5).',
225
+ },
226
+ },
227
+ output: {
228
+ schema: {
229
+ type: 'object',
230
+ additionalProperties: false,
231
+ properties: {
232
+ query: { type: 'string', required: true },
233
+ results: {
234
+ type: 'array',
235
+ required: true,
236
+ items: {
237
+ type: 'object',
238
+ additionalProperties: false,
239
+ properties: {
240
+ name: { type: 'string', required: true },
241
+ description: { type: 'string', required: true },
242
+ status: {
243
+ type: 'string',
244
+ required: true,
245
+ enum: ['loaded', 'already-loaded', 'unknown', 'denied'],
246
+ },
247
+ reason: { type: 'string' },
248
+ },
249
+ },
250
+ },
251
+ },
252
+ },
253
+ render: (_args, value) => [{
254
+ type: 'text',
255
+ text: renderToolSearchText(value.query, value.results),
256
+ }],
257
+ },
258
+ // Arrow bodies keep `this` bound to the registry service; the schema
259
+ // narrows args to match the parameter spec (`query` required, `max_results`
260
+ // optional).
261
+ execute: (args, exec) => {
262
+ const scope = exec.agent;
263
+ const maxResults = args.max_results ?? 5;
264
+ const hits = this.search(args.query, maxResults, scope);
265
+ const results = hits.map((hit) => {
266
+ const outcome = this.activate(hit.name, scope);
267
+ return {
268
+ name: outcome.name,
269
+ description: hit.description,
270
+ status: outcome.status,
271
+ ...outcome.status === 'denied' ? { reason: outcome.reason } : {},
272
+ };
273
+ });
274
+ return Promise.resolve({ query: args.query, results });
275
+ },
276
+ presentCall: (args) => ({ card: 'generic', title: 'Search deferred tools', kind: 'search', rawInput: args.query }),
277
+ presentResult: (_args, result) => ({ card: 'generic', title: 'Tool search', content: result.content }),
278
+ });
279
+ }
280
+ }
281
+ /** Compose the model-facing summary of a ToolSearch outcome. */
282
+ function renderToolSearchText(query, results) {
283
+ if (results.length === 0) {
284
+ return `No deferred tools matched "${query}".`;
285
+ }
286
+ const lines = results.map((result) => {
287
+ const statusLabel = result.status === 'loaded' ? 'now available'
288
+ : result.status === 'already-loaded' ? 'already available'
289
+ : result.status === 'denied' ? 'denied'
290
+ : 'not found';
291
+ const tail = result.status === 'denied' && result.reason !== undefined ? ` — ${result.reason}` : '';
292
+ return `- ${result.name}: ${result.description} (${statusLabel})${tail}`;
293
+ });
294
+ return `ToolSearch "${query}":\n${lines.join('\n')}`;
295
+ }
296
+ export default DeferredToolRegistry;
297
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@jianxx/dsh-cc-tool-search`.
3
+ * @module @jianxx/dsh-cc-tool-search/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "tool-search-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariant.d.ts","sourceRoot":"","sources":["../src/invariant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,oCAAoC;AACpC,eAAO,MAAM,IAAI,0BAA0B,CAAA;AAC3C,2EAA2E;AAC3E,eAAO,MAAM,MAAM,UAAiB,CAAA;AAUpC;;;;GAIG;AACH,eAAO,MAAM,KAAK,GAAI,KAAK,OAAO,KAAG,OAAO,CAAC,MAAM,IAAI,CACU,CAAA"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Package-owned invariant companion for `@jianxx/dsh-cc-tool-search`.
3
+ * @module @jianxx/dsh-cc-tool-search/invariant
4
+ */
5
+ const PACKAGE_NAME = '@jianxx/dsh-cc-tool-search';
6
+ /** Cordis companion plugin name. */
7
+ export const name = 'tool-search-invariant';
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export const inject = ['invariants'];
10
+ /**
11
+ * No runtime invariant: this package owns no event or snapshot of its own. The
12
+ * relation it establishes — a deferred tool being loaded into the tool registry
13
+ * after a ToolSearch hit — is the tool registry's to hold, and `dsh-tools`
14
+ * observes registration there through `tools/change`.
15
+ */
16
+ const install = () => { };
17
+ /**
18
+ * Register this package's invariant companion.
19
+ * @param ctx - Cordis context carrying the invariant service.
20
+ * @returns the installed registration's disposer after setup succeeds.
21
+ */
22
+ export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
23
+ /* jscpd:ignore-end */
24
+ //# sourceMappingURL=invariant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariant.js","sourceRoot":"","sources":["../src/invariant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,YAAY,GAAG,4BAA4B,CAAA;AAEjD,oCAAoC;AACpC,MAAM,CAAC,MAAM,IAAI,GAAG,uBAAuB,CAAA;AAC3C,2EAA2E;AAC3E,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,YAAY,CAAC,CAAA;AAEpC;;;;;GAKG;AACH,MAAM,OAAO,GAAuB,GAAG,EAAE,GAAE,CAAC,CAAA;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,GAAY,EAAuB,EAAE,CACzD,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;AACjE,sBAAsB"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@jianxx/dsh-cc-tool-search",
3
+ "description": "Deferred tool registration and a ToolSearch model tool that loads heavy tools on demand",
4
+ "version": "0.1.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/jianxx/dsh-cc-plugins.git",
8
+ "directory": "packages/core/tool-search"
9
+ },
10
+ "type": "module",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/index.d.ts",
14
+ "default": "./lib/index.js"
15
+ },
16
+ "./invariant": {
17
+ "types": "./lib/invariant.d.ts",
18
+ "default": "./lib/invariant.js"
19
+ },
20
+ "./src/*": "./src/*",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "lib"
25
+ ],
26
+ "license": "Apache-2.0",
27
+ "peerDependencies": {
28
+ "@deepseek-ai/dsh-invariants": ">=0.1.1-rc.2",
29
+ "@deepseek-ai/dsh-scope": ">=0.1.1-rc.2",
30
+ "@deepseek-ai/dsh-system-prompt": ">=0.1.1-rc.2",
31
+ "@deepseek-ai/cordis": ">=0.1.1-rc.2",
32
+ "@jianxx/dsh-cc-tools": "^0.1.0"
33
+ },
34
+ "devDependencies": {
35
+ "@deepseek-ai/dsh-agent": "link:../../../../deepseek-harness/packages/core/agent",
36
+ "@deepseek-ai/dsh-invariants": "link:../../../../deepseek-harness/packages/runtime-diagnostics/invariants",
37
+ "@deepseek-ai/dsh-llm": "link:../../../../deepseek-harness/packages/llm/llm",
38
+ "@deepseek-ai/dsh-scope": "link:../../../../deepseek-harness/packages/core/scope",
39
+ "@deepseek-ai/dsh-session": "link:../../../../deepseek-harness/packages/core/session",
40
+ "@deepseek-ai/dsh-system-prompt": "link:../../../../deepseek-harness/packages/core/system-prompt",
41
+ "@deepseek-ai/cordis": "link:../../../../deepseek-harness/vendor/cordis",
42
+ "@jianxx/dsh-cc-tools": "^0.1.0"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }