@4fu/dsh-arbor-tools 0.0.0-stage → 2026.1110.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.md +249 -2
- package/index.d.ts +108 -0
- package/index.js +220 -0
- package/package.json +38 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 4fuu
|
|
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.md
CHANGED
|
@@ -1,3 +1,250 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-arbor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run [arbor](https://github.com/4fuu/arbor) as the agent driver inside
|
|
4
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH).
|
|
5
|
+
|
|
6
|
+
DSH gives a user a session, a transcript, approvals, plugins, skills and a model
|
|
7
|
+
picker. arbor gives an agent disposable context windows over one shared journal,
|
|
8
|
+
note store and workspace, instead of one long conversation. dsh-arbor connects
|
|
9
|
+
them: one arbor host runs inside the DSH process, each DSH session that selects
|
|
10
|
+
**Arbor** is served by that host, and the window's reply is written back to the
|
|
11
|
+
DSH session as an ordinary assistant message. A DSH session that does not select
|
|
12
|
+
Arbor keeps running on DSH's own embedded AgentLoop, in the same process.
|
|
13
|
+
|
|
14
|
+
> **Status.** This is a research-grade integration, not a DSH release. It is
|
|
15
|
+
> pinned to one DSH version and relies on DSH internals. Read
|
|
16
|
+
> [Versions and compatibility](docs/versions.md) before installing it on
|
|
17
|
+
> anything you care about. The packages are published under `@4fu` at
|
|
18
|
+
> `2026.1110.1`; the bundle and the font plugin install independently from npm.
|
|
19
|
+
|
|
20
|
+
## What an arbor session changes
|
|
21
|
+
|
|
22
|
+
Choosing **Arbor** in a DSH composer creates the session under the `arbor` agent
|
|
23
|
+
preset. That session then runs on arbor, and the following are true for it:
|
|
24
|
+
|
|
25
|
+
- **One window per turn, one chain per session.** A DSH session is one arbor
|
|
26
|
+
conversation: its first turn opens a window, a window that ends hands off to a
|
|
27
|
+
continuation, and the next turn drives the chain. The journal and note store
|
|
28
|
+
live under `$DSH_HOME/arbor/` and survive a restart.
|
|
29
|
+
- **arbor's workspace, not DSH's sandbox.** A window works in arbor's shared
|
|
30
|
+
folder (`$DSH_HOME/arbor/shared`). When the `ovnfs` binary is available a
|
|
31
|
+
window opens a copy-on-write checkout and merges back with `apply`; otherwise
|
|
32
|
+
arbor uses a plain directory. **There is no DSH sandbox for an arbor session:**
|
|
33
|
+
arbor's file tools and commands run on the host, and arbor's own secret scrub
|
|
34
|
+
is the only protection on what reaches the model.
|
|
35
|
+
- **arbor's projects.** The shared folder's project folders are the DSH project
|
|
36
|
+
list for arbor composers. A directory the user adds outside the shared folder
|
|
37
|
+
is recorded as an external pointer for that session and used in place.
|
|
38
|
+
- **arbor's prompt and tools.** DSH's official prompt sections are decided one by
|
|
39
|
+
one; the ones arbor replaces are absent, third-party sections go into a
|
|
40
|
+
`<dsh>` data block, and a trusted `<harness>` section says what remains true of
|
|
41
|
+
DSH. DSH tools are offered to the model as arbor external tools, and arbor's
|
|
42
|
+
own tools replace DSH tools with the same capability.
|
|
43
|
+
- **The Arbor page.** A permanent **Arbor** entry in DSH's left navigation opens
|
|
44
|
+
the whole-host overview and one tab per arbor session: windows, chains,
|
|
45
|
+
handoffs, projects, the board, notes, `/env`, schedules and arbor's search.
|
|
46
|
+
An arbor composer hides DSH's sandbox access-mode switch, because that switch
|
|
47
|
+
does not apply to it.
|
|
48
|
+
|
|
49
|
+
## What stays untouched in ordinary DSH sessions
|
|
50
|
+
|
|
51
|
+
A session that does not select Arbor is an ordinary DSH session. It keeps:
|
|
52
|
+
|
|
53
|
+
- DSH's embedded AgentLoop, session format, turn boundaries and streaming path;
|
|
54
|
+
- DSH's sandbox policy and its `ctx.fs` / `ctx.shell` file and command tools;
|
|
55
|
+
- DSH's project management, working directory and prompt assembly;
|
|
56
|
+
- its composer, sandbox access-mode switch, header and sidebar behavior.
|
|
57
|
+
|
|
58
|
+
It gets **no Arbor tab** and no arbor windows. The only shared surface is the
|
|
59
|
+
permanent Arbor entry in the left navigation, which simply does not list that
|
|
60
|
+
session. The routing registry replaces DSH's base `agent` row so it can send
|
|
61
|
+
`agentPreset: 'arbor'` sessions to arbor; every other session goes to the
|
|
62
|
+
official loop. Approvals still use DSH's approval path for both kinds of
|
|
63
|
+
session, and arbor's windows are registered so DSH's sidebar leaves them out.
|
|
64
|
+
|
|
65
|
+
## Screenshots
|
|
66
|
+
|
|
67
|
+
| Overview, light | Overview, dark |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
|  |  |
|
|
70
|
+
| **Session tab, light** | **Session tab, dark** |
|
|
71
|
+
|  |  |
|
|
72
|
+
|
|
73
|
+
These are the web profile at 1440 px wide with the optional
|
|
74
|
+
[font plugin](https://github.com/4fuu/dsh-arbor-fonts) installed: ChillDIN
|
|
75
|
+
Gothic for labels and ChillDIN for numerals, while body text and code keep
|
|
76
|
+
DSH's fonts. The same overview without the plugin is
|
|
77
|
+
[here](docs/screenshots/overview-without-fonts.png).
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
Requirements: Node.js 24.9 or newer and DeepSeek Harness `dsh-v0.2.2-rc.1`.
|
|
82
|
+
The bundle is published with all packages it depends on; nothing has to be
|
|
83
|
+
built or linked.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm install -g @deepseek-ai/dsh@0.2.2-rc.1
|
|
87
|
+
dsh plugin --profile web add @4fu/dsh-arbor-bundle@2026.1110.1
|
|
88
|
+
dsh web
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`dsh plugin` adds the bundle to the profile's `dsh.profile.bundles` and installs
|
|
92
|
+
its packages into the profile. The packages declare DSH's runtime packages as
|
|
93
|
+
peer dependencies, so the profile keeps DSH's own copies rather than installing
|
|
94
|
+
a second set. Pick **Arbor** in a composer and send a message.
|
|
95
|
+
The optional [font plugin](https://github.com/4fuu/dsh-arbor-fonts) installs the
|
|
96
|
+
same way, but it is a Host row rather than a bundle. Add the row to the
|
|
97
|
+
profile's `cordis.patch.yml` after installing it:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
dsh plugin --profile web add @4fu/dsh-arbor-fonts@2026.1110.0
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
- insert:
|
|
105
|
+
- id: arbor-fonts
|
|
106
|
+
name: '@4fu/dsh-arbor-fonts'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Replace the default `[]` in that file with the entry, then restart the profile.
|
|
110
|
+
|
|
111
|
+
### From a checkout
|
|
112
|
+
|
|
113
|
+
The proofs and development use the TypeScript sources directly; that checkout is
|
|
114
|
+
what the scripts and the pin check run against.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git clone https://github.com/4fuu/dsh-arbor
|
|
118
|
+
cd dsh-arbor
|
|
119
|
+
pnpm install --frozen-lockfile
|
|
120
|
+
pnpm check # tsc --noEmit over every package and proof
|
|
121
|
+
pnpm proofs # 29 proofs against the scripted model
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The first install may ask pnpm to approve dependency build scripts. Do not
|
|
125
|
+
approve them by hand: the repository's `pnpm-workspace.yaml` already denies the
|
|
126
|
+
six scripts it pulls in, each with the reason, and `pnpm install` finishes
|
|
127
|
+
cleanly with that policy.
|
|
128
|
+
|
|
129
|
+
### Hand the install to a model
|
|
130
|
+
|
|
131
|
+
[docs/install-for-agents.md](docs/install-for-agents.md) is a step-by-step guide
|
|
132
|
+
written for an AI agent: install from npm or a checkout, run the proofs, drive a
|
|
133
|
+
live turn with a restart, boot the web profile, and inspect what ran. Give a
|
|
134
|
+
model this prompt, editing the bracketed parts or dropping them:
|
|
135
|
+
|
|
136
|
+
```text copy Copy Install Prompt
|
|
137
|
+
Install dsh-arbor (https://github.com/4fuu/dsh-arbor) in this environment [in a
|
|
138
|
+
scratch DSH home under /tmp] and show me that it works: follow
|
|
139
|
+
docs/install-for-agents.md, install @4fu/dsh-arbor-bundle@2026.1110.1 into a DSH
|
|
140
|
+
web profile, pick Arbor for one session, and [also run the proof suite from a
|
|
141
|
+
checkout with pnpm install --frozen-lockfile, pnpm check and pnpm proofs]. Report
|
|
142
|
+
which checks passed, what the live session answered, and anything that did not
|
|
143
|
+
work. Fix only what is yours to fix and tell me what you changed.
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Use it in DSH
|
|
147
|
+
|
|
148
|
+
The npm install above adds the bundle to a DSH web profile. Pick **Arbor** in a
|
|
149
|
+
composer and send a message: the session is created under the preset, its window
|
|
150
|
+
and chain appear under the **Arbor** entry, and an ordinary session started
|
|
151
|
+
beside it keeps DSH's own composer and sandbox behavior. A checkout can also be
|
|
152
|
+
wired by hand; [section 7 of the install guide](docs/install-for-agents.md#7-wiring-the-bundle-into-a-real-dsh-installation)
|
|
153
|
+
shows the profile layout.
|
|
154
|
+
|
|
155
|
+
The one-shot `dsh <task>` runner cannot drive an arbor-preset session: DSH's
|
|
156
|
+
runner refuses to adopt a session that runs under a preset. Use the web client,
|
|
157
|
+
the SDK path the proofs use, or a remote `agent-preset/select` on a blank.
|
|
158
|
+
|
|
159
|
+
## Pinned DSH version
|
|
160
|
+
|
|
161
|
+
DSH is pinned to **`dsh-v0.2.2-rc.1`** (npm
|
|
162
|
+
`@deepseek-ai/dsh@0.2.2-rc.1`, commit `66491b05e3` of
|
|
163
|
+
`deepseek-ai/deepseek-harness`). Every `@deepseek-ai/dsh*` dependency is an
|
|
164
|
+
exact version, every package under `packages/` declares
|
|
165
|
+
`engines.dsh: 0.2.2-rc.1`, and `pnpm check:pin` checks both plus the prompt
|
|
166
|
+
bridge's `DSH_PINNED_VERSION`. DSH's runtime packages are peer dependencies, and
|
|
167
|
+
`pnpm check:profile` installs the packed packages into a fresh DSH web profile to
|
|
168
|
+
prove the profile keeps one copy and can create both kinds of session.
|
|
169
|
+
|
|
170
|
+
A different DSH version is unsupported. The bundle depends on DSH's row names,
|
|
171
|
+
its one-`AgentFactory` rule, its prompt-section registry, its client slot
|
|
172
|
+
registration and its exact Fetch registry; a move in any of them can fail the
|
|
173
|
+
boot, leave an official prompt section undecided, or stop the Arbor page from
|
|
174
|
+
registering. `proofs/12-official-sections.ts` and `proofs/18-picker-pin.ts`
|
|
175
|
+
are the behavioral checks. The canonical pin and the upgrade procedure are in
|
|
176
|
+
[Versions and compatibility](docs/versions.md).
|
|
177
|
+
|
|
178
|
+
## Optional: the font plugin
|
|
179
|
+
|
|
180
|
+
[dsh-arbor-fonts](https://github.com/4fuu/dsh-arbor-fonts) is a separate DSH
|
|
181
|
+
client plugin that changes only the typography of the Arbor page. It sets
|
|
182
|
+
ChillDIN Gothic for display labels and ChillDIN for numerals, leaves body text
|
|
183
|
+
and code on DSH's fonts, and does nothing on ordinary pages. It is optional and
|
|
184
|
+
installs into the same DSH profile:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
dsh plugin --profile web add @4fu/dsh-arbor-fonts@2026.1110.0
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The fonts are licensed under the SIL Open Font License 1.1; the plugin ships the
|
|
191
|
+
license text, per-file hashes and attribution. The plugin is a Host row rather
|
|
192
|
+
than a bundle: after installing it, add the `arbor-fonts` entry described under
|
|
193
|
+
[Install](#install) to `cordis.patch.yml` and restart the profile.
|
|
194
|
+
|
|
195
|
+
## Limits worth knowing
|
|
196
|
+
|
|
197
|
+
- The judge is off: a model-based judge cannot run through the DSH backend yet.
|
|
198
|
+
- The 12 bundle packages are published together at one version; install and
|
|
199
|
+
upgrade the bundle as a unit, because its packages name each other exactly.
|
|
200
|
+
- `ovnfs` is optional. Without it arbor uses the plain-directory workspace, and a
|
|
201
|
+
window no longer gets a copy-on-write checkout.
|
|
202
|
+
- The web page's numbers and layout are read from arbor's journal and API; it is
|
|
203
|
+
not a generic DSH dashboard, and it deliberately shows only arbor sessions.
|
|
204
|
+
- The prompt bridge, the UI and the picker filter are pinned to DSH's internals,
|
|
205
|
+
not to a stable public API. See [Versions and compatibility](docs/versions.md).
|
|
206
|
+
|
|
207
|
+
## Documentation
|
|
208
|
+
|
|
209
|
+
- [Installing and inspecting dsh-arbor](docs/install-for-agents.md) — the
|
|
210
|
+
agent-facing install, proof, live-turn, web-profile and wiring guide.
|
|
211
|
+
- [Versions and compatibility](docs/versions.md) — the DSH and arbor pins, and
|
|
212
|
+
what breaks on another DSH version.
|
|
213
|
+
- [B0 findings](docs/b0-findings.md), [B3 findings](docs/b3-findings.md) and
|
|
214
|
+
[B3b projects and sandbox](docs/b3b-projects-and-sandbox.md) — historical
|
|
215
|
+
records of the integration's decisions, with the DSH source locations each
|
|
216
|
+
one rests on. They describe earlier stages; the install guide is the current
|
|
217
|
+
status.
|
|
218
|
+
- [Layout and proofs](#layout) below for a quick map of the packages.
|
|
219
|
+
|
|
220
|
+
## Layout
|
|
221
|
+
|
|
222
|
+
```text
|
|
223
|
+
packages/
|
|
224
|
+
arbor-host/ one Arbor host per process: SQLite journal and notes,
|
|
225
|
+
settings, window lifecycle, workspace and environment
|
|
226
|
+
arbor-driver/ the routing AgentRegistry, AgentFactory, session agent,
|
|
227
|
+
inbox and stream mirrors, and the journal relay
|
|
228
|
+
arbor-llm/ the arbor AgentBackend over DSH's ctx.llm and ctx.tools
|
|
229
|
+
arbor-prompt/ <harness>, the <dsh> data block, and the section table
|
|
230
|
+
arbor-tools/ DSH tools as arbor external tools, same-name replacement
|
|
231
|
+
arbor-workspace/ arbor's workspace over the shared folder (ovnfs or dir)
|
|
232
|
+
arbor-env/ /env, with DSH's skills and MCP servers mounted read-only
|
|
233
|
+
arbor-bundle/ the installable patch that mounts the rows and UI
|
|
234
|
+
arbor-facts/ the read face behind the Arbor page and its Fetch routes
|
|
235
|
+
arbor-ui/ the Arbor page, session tabs, copy and composer gate
|
|
236
|
+
arbor-ui-workspace/
|
|
237
|
+
the per-preset hero project picker
|
|
238
|
+
arbor-preset/ the agent-preset row that puts Arbor in the roster
|
|
239
|
+
arbor-testkit/ the boot harness, memory journal and scripted model
|
|
240
|
+
patches/ overlay layers a proof applies above the shipped profile
|
|
241
|
+
proofs/ one runnable proof per integration point
|
|
242
|
+
docs/ install, versions, screenshots and historical findings
|
|
243
|
+
scripts/ the pin and profile checks, the release build and packing
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT. See [LICENSE](LICENSE). The optional font plugin vendors fonts under the
|
|
249
|
+
SIL Open Font License 1.1; its repository carries the license text and
|
|
250
|
+
attribution.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool bridge: DSH's registered tools enter arbor as external tools and run
|
|
3
|
+
* through `ctx.tools.execute`.
|
|
4
|
+
*
|
|
5
|
+
* `AgentBackend.externalTools()` (arbor-interfaces/agent.d.ts) is the seam the
|
|
6
|
+
* plan names: the host registers what it is handed, and from there a DSH tool
|
|
7
|
+
* is a direct tool like any other — declared to the model under its own name
|
|
8
|
+
* and called through the same hooks, journaling, scrub and result cut as
|
|
9
|
+
* arbor's own. Execution lands back in DSH: `ctx.tools.execute` with the user
|
|
10
|
+
* session's agent, which is what routes a tool call that needs approval
|
|
11
|
+
* through DSH's approval path (`packages/core/tools/src/pipeline.ts`
|
|
12
|
+
* serviceAsk → `packages/interaction/user-approval`), and appends the durable
|
|
13
|
+
* `tool/call` + `tool/result` pair to the user session's log.
|
|
14
|
+
*
|
|
15
|
+
* Two things make that seam faithful rather than lossy:
|
|
16
|
+
*
|
|
17
|
+
* - **The schema is carried verbatim.** A DSH tool declares its parameters as
|
|
18
|
+
* JSON Schema; arbor's `ExternalTool.schema` is exactly that, so `enum`,
|
|
19
|
+
* `items`, `oneOf`, `additionalProperties` and a nested object's own
|
|
20
|
+
* properties all reach the model instead of being flattened away.
|
|
21
|
+
* - **Every capability appears once.** {@link DSH_TOOLS_ARBOR_REPLACES} names
|
|
22
|
+
* the DSH tools whose capability arbor already has, so they are neither
|
|
23
|
+
* handed to the catalog nor offered by DSH's own pipeline: the driver
|
|
24
|
+
* installs the same list as a `tools.restrict` in each arbor session's
|
|
25
|
+
* scope, which is also what makes their `tool:*` prompt sections render
|
|
26
|
+
* empty.
|
|
27
|
+
*/
|
|
28
|
+
import type { AgentRegistry } from '@deepseek-ai/dsh-agent';
|
|
29
|
+
import type { ToolsService } from '@deepseek-ai/dsh-tools';
|
|
30
|
+
import type { ExternalTool, DirectToolSpec } from '@4fu/arbor-interfaces';
|
|
31
|
+
/**
|
|
32
|
+
* The DSH tools arbor's own replaces, one capability each.
|
|
33
|
+
*
|
|
34
|
+
* - `bash`, `pwsh` — arbor's `bash`, `bash_output`, `bash_stop`.
|
|
35
|
+
* - `read`, `write`, `edit`, `apply_patch` — arbor's file tools.
|
|
36
|
+
* - `glob`, `grep` — arbor's `workspace` module.
|
|
37
|
+
* - `job_start`, `job_output`, `job_kill`, `job_list` — arbor's
|
|
38
|
+
* `bash_output` / `bash_stop`.
|
|
39
|
+
* - `subagent`, `subagent_fork` — arbor's windows, its broadcast and its
|
|
40
|
+
* window messages.
|
|
41
|
+
* - `schedule`, `schedule_*` — arbor's own `schedule` module. DSH's scheduler
|
|
42
|
+
* opens another DSH session, which fights arbor's source model.
|
|
43
|
+
* - `create_goal`, `get_goal`, `update_goal` — goals are closed for arbor
|
|
44
|
+
* sessions.
|
|
45
|
+
* - `team_*`, `send_message`, `list_agents`, `interrupt_agent` — teams are
|
|
46
|
+
* closed for arbor sessions.
|
|
47
|
+
* - `ralph` — closed for arbor sessions.
|
|
48
|
+
* - `workflow` — closed for arbor sessions.
|
|
49
|
+
* - `session_search`, `session_trace`, `session_event_search`,
|
|
50
|
+
* `session_event_read`, `session_event_trace` — history is arbor's journal.
|
|
51
|
+
* - `structured_output` — only DSH's own subagents use it.
|
|
52
|
+
* - `run_code` — arbor has its own `code_exec`; DSH's PTC executor is not used.
|
|
53
|
+
* - `working_directory` — arbor owns the paths for an arbor session.
|
|
54
|
+
* - `skill` — reading the SKILL.md `<env>` names, which arbor's `read` does.
|
|
55
|
+
*
|
|
56
|
+
* `read_image` is deliberately absent: it returns the image itself, which
|
|
57
|
+
* arbor's text-only `read` cannot, so it is a capability of its own rather than
|
|
58
|
+
* a duplicate.
|
|
59
|
+
*
|
|
60
|
+
* The list is filtered against what the deployment actually registers before
|
|
61
|
+
* it is used, because `tools.restrict` rejects an unknown name.
|
|
62
|
+
*/
|
|
63
|
+
export declare const DSH_TOOLS_ARBOR_REPLACES: readonly string[];
|
|
64
|
+
/**
|
|
65
|
+
* The subset of {@link DSH_TOOLS_ARBOR_REPLACES} the deployment registers.
|
|
66
|
+
*
|
|
67
|
+
* Filtered, because `tools.restrict` throws on a name that is not a known
|
|
68
|
+
* global tool, and a deployment mounts only part of DSH.
|
|
69
|
+
*/
|
|
70
|
+
export declare function replacedToolsRegistered(tools: ToolsService): readonly string[];
|
|
71
|
+
/** One arbor direct-tool spec back to a DSH tool schema for the model request. */
|
|
72
|
+
export declare function schemaOf(spec: DirectToolSpec): Record<string, unknown>;
|
|
73
|
+
/**
|
|
74
|
+
* The live turn's abort signal, which a running session agent exposes so a
|
|
75
|
+
* tool call is aborted with the turn that asked for it. The arbor session
|
|
76
|
+
* agent implements it; any other agent simply does not, and the call then runs
|
|
77
|
+
* on a signal of its own, the way a standalone call does.
|
|
78
|
+
*/
|
|
79
|
+
export interface LiveTurnSignal {
|
|
80
|
+
currentSignal(): AbortSignal | undefined;
|
|
81
|
+
}
|
|
82
|
+
/** Inputs for {@link dshExternalTools}. */
|
|
83
|
+
export interface DshExternalToolsOptions {
|
|
84
|
+
/** DSH's tool registry. */
|
|
85
|
+
readonly tools: ToolsService;
|
|
86
|
+
/**
|
|
87
|
+
* DSH's agent registry. One backend serves every session, so the agent a
|
|
88
|
+
* call runs as is read per call: the initiating agent of the turn executing
|
|
89
|
+
* it, which is the session whose window asked for the call.
|
|
90
|
+
*/
|
|
91
|
+
readonly agents: AgentRegistry;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Every registered DSH tool arbor does not already have, as arbor external
|
|
95
|
+
* tools.
|
|
96
|
+
*
|
|
97
|
+
* The specs are host-level and session-free: the same declarations reach the
|
|
98
|
+
* model whichever session's window is running, and each call executes as the
|
|
99
|
+
* session whose turn is running it. With no initiating agent — a call outside
|
|
100
|
+
* any turn — DSH's own pipeline degrades the execution to a deny, which is the
|
|
101
|
+
* correct fail-closed answer.
|
|
102
|
+
*
|
|
103
|
+
* The execution carries no log position, so DSH's pipeline does not write the
|
|
104
|
+
* `tool/call` + `tool/result` pair: the driver's bridge writes one pair for
|
|
105
|
+
* every call the window runs, arbor's own tools included, because the session
|
|
106
|
+
* log is the history the next model request is derived from.
|
|
107
|
+
*/
|
|
108
|
+
export declare function dshExternalTools(options: DshExternalToolsOptions): readonly ExternalTool[];
|
package/index.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool bridge: DSH's registered tools enter arbor as external tools and run
|
|
3
|
+
* through `ctx.tools.execute`.
|
|
4
|
+
*
|
|
5
|
+
* `AgentBackend.externalTools()` (arbor-interfaces/agent.d.ts) is the seam the
|
|
6
|
+
* plan names: the host registers what it is handed, and from there a DSH tool
|
|
7
|
+
* is a direct tool like any other — declared to the model under its own name
|
|
8
|
+
* and called through the same hooks, journaling, scrub and result cut as
|
|
9
|
+
* arbor's own. Execution lands back in DSH: `ctx.tools.execute` with the user
|
|
10
|
+
* session's agent, which is what routes a tool call that needs approval
|
|
11
|
+
* through DSH's approval path (`packages/core/tools/src/pipeline.ts`
|
|
12
|
+
* serviceAsk → `packages/interaction/user-approval`), and appends the durable
|
|
13
|
+
* `tool/call` + `tool/result` pair to the user session's log.
|
|
14
|
+
*
|
|
15
|
+
* Two things make that seam faithful rather than lossy:
|
|
16
|
+
*
|
|
17
|
+
* - **The schema is carried verbatim.** A DSH tool declares its parameters as
|
|
18
|
+
* JSON Schema; arbor's `ExternalTool.schema` is exactly that, so `enum`,
|
|
19
|
+
* `items`, `oneOf`, `additionalProperties` and a nested object's own
|
|
20
|
+
* properties all reach the model instead of being flattened away.
|
|
21
|
+
* - **Every capability appears once.** {@link DSH_TOOLS_ARBOR_REPLACES} names
|
|
22
|
+
* the DSH tools whose capability arbor already has, so they are neither
|
|
23
|
+
* handed to the catalog nor offered by DSH's own pipeline: the driver
|
|
24
|
+
* installs the same list as a `tools.restrict` in each arbor session's
|
|
25
|
+
* scope, which is also what makes their `tool:*` prompt sections render
|
|
26
|
+
* empty.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* The DSH tools arbor's own replaces, one capability each.
|
|
30
|
+
*
|
|
31
|
+
* - `bash`, `pwsh` — arbor's `bash`, `bash_output`, `bash_stop`.
|
|
32
|
+
* - `read`, `write`, `edit`, `apply_patch` — arbor's file tools.
|
|
33
|
+
* - `glob`, `grep` — arbor's `workspace` module.
|
|
34
|
+
* - `job_start`, `job_output`, `job_kill`, `job_list` — arbor's
|
|
35
|
+
* `bash_output` / `bash_stop`.
|
|
36
|
+
* - `subagent`, `subagent_fork` — arbor's windows, its broadcast and its
|
|
37
|
+
* window messages.
|
|
38
|
+
* - `schedule`, `schedule_*` — arbor's own `schedule` module. DSH's scheduler
|
|
39
|
+
* opens another DSH session, which fights arbor's source model.
|
|
40
|
+
* - `create_goal`, `get_goal`, `update_goal` — goals are closed for arbor
|
|
41
|
+
* sessions.
|
|
42
|
+
* - `team_*`, `send_message`, `list_agents`, `interrupt_agent` — teams are
|
|
43
|
+
* closed for arbor sessions.
|
|
44
|
+
* - `ralph` — closed for arbor sessions.
|
|
45
|
+
* - `workflow` — closed for arbor sessions.
|
|
46
|
+
* - `session_search`, `session_trace`, `session_event_search`,
|
|
47
|
+
* `session_event_read`, `session_event_trace` — history is arbor's journal.
|
|
48
|
+
* - `structured_output` — only DSH's own subagents use it.
|
|
49
|
+
* - `run_code` — arbor has its own `code_exec`; DSH's PTC executor is not used.
|
|
50
|
+
* - `working_directory` — arbor owns the paths for an arbor session.
|
|
51
|
+
* - `skill` — reading the SKILL.md `<env>` names, which arbor's `read` does.
|
|
52
|
+
*
|
|
53
|
+
* `read_image` is deliberately absent: it returns the image itself, which
|
|
54
|
+
* arbor's text-only `read` cannot, so it is a capability of its own rather than
|
|
55
|
+
* a duplicate.
|
|
56
|
+
*
|
|
57
|
+
* The list is filtered against what the deployment actually registers before
|
|
58
|
+
* it is used, because `tools.restrict` rejects an unknown name.
|
|
59
|
+
*/
|
|
60
|
+
export const DSH_TOOLS_ARBOR_REPLACES = [
|
|
61
|
+
// bash / pwsh
|
|
62
|
+
'bash',
|
|
63
|
+
'pwsh',
|
|
64
|
+
// read / write / edit / apply_patch
|
|
65
|
+
'read',
|
|
66
|
+
'write',
|
|
67
|
+
'edit',
|
|
68
|
+
'apply_patch',
|
|
69
|
+
// glob / grep
|
|
70
|
+
'glob',
|
|
71
|
+
'grep',
|
|
72
|
+
// job_*
|
|
73
|
+
'job_start',
|
|
74
|
+
'job_output',
|
|
75
|
+
'job_kill',
|
|
76
|
+
'job_list',
|
|
77
|
+
// subagent / subagent_fork
|
|
78
|
+
'subagent',
|
|
79
|
+
'subagent_fork',
|
|
80
|
+
// schedule_*
|
|
81
|
+
'schedule',
|
|
82
|
+
'schedule_create',
|
|
83
|
+
'schedule_edit',
|
|
84
|
+
'schedule_list',
|
|
85
|
+
'schedule_view',
|
|
86
|
+
'schedule_delete',
|
|
87
|
+
'schedule_execution_pending',
|
|
88
|
+
// goal
|
|
89
|
+
'create_goal',
|
|
90
|
+
'get_goal',
|
|
91
|
+
'update_goal',
|
|
92
|
+
// team
|
|
93
|
+
'team_task_create',
|
|
94
|
+
'team_task_get',
|
|
95
|
+
'team_task_list',
|
|
96
|
+
'team_task_update',
|
|
97
|
+
'send_message',
|
|
98
|
+
'list_agents',
|
|
99
|
+
'interrupt_agent',
|
|
100
|
+
// ralph
|
|
101
|
+
'ralph',
|
|
102
|
+
// workflow
|
|
103
|
+
'workflow',
|
|
104
|
+
// session-query
|
|
105
|
+
'session_search',
|
|
106
|
+
'session_trace',
|
|
107
|
+
'session_event_search',
|
|
108
|
+
'session_event_read',
|
|
109
|
+
'session_event_trace',
|
|
110
|
+
// structured output and PTC
|
|
111
|
+
'structured_output',
|
|
112
|
+
'run_code',
|
|
113
|
+
// DSH's project management, which arbor owns for an arbor session: the
|
|
114
|
+
// paths are arbor's own `<workspace>` section's — the shared folder — so a
|
|
115
|
+
// `cd` would move ground the section does not describe. A UI switch still
|
|
116
|
+
// lands as a `working-directory/change` event, but an arbor session is not
|
|
117
|
+
// bound to one cwd: its window works in the shared folder and its checkouts,
|
|
118
|
+
// so the event moves the session's cwd display and nothing a window does.
|
|
119
|
+
'working_directory',
|
|
120
|
+
// DSH's skill loader, which is `read` of the SKILL.md `<env>` already names:
|
|
121
|
+
// the skill's body is a file under /env, and arbor's own `read` reaches it.
|
|
122
|
+
// `read_image` is NOT here: it returns the image itself, which arbor's
|
|
123
|
+
// text-only `read` cannot do.
|
|
124
|
+
'skill',
|
|
125
|
+
];
|
|
126
|
+
/**
|
|
127
|
+
* The subset of {@link DSH_TOOLS_ARBOR_REPLACES} the deployment registers.
|
|
128
|
+
*
|
|
129
|
+
* Filtered, because `tools.restrict` throws on a name that is not a known
|
|
130
|
+
* global tool, and a deployment mounts only part of DSH.
|
|
131
|
+
*/
|
|
132
|
+
export function replacedToolsRegistered(tools) {
|
|
133
|
+
const registered = new Set(tools.schemas().map(schema => schema.name));
|
|
134
|
+
return DSH_TOOLS_ARBOR_REPLACES.filter(name => registered.has(name));
|
|
135
|
+
}
|
|
136
|
+
/** One arbor direct-tool spec back to a DSH tool schema for the model request. */
|
|
137
|
+
export function schemaOf(spec) {
|
|
138
|
+
// A tool declared from outside the core carries DSH's own JSON Schema
|
|
139
|
+
// verbatim, and that is what the model reads. Arbor's own tools declare
|
|
140
|
+
// none: their `parameters` are the declaration, and the shape is built here.
|
|
141
|
+
if (spec.schema !== undefined) {
|
|
142
|
+
return { name: spec.name, description: spec.description, parameters: spec.schema };
|
|
143
|
+
}
|
|
144
|
+
const properties = {};
|
|
145
|
+
const required = [];
|
|
146
|
+
for (const [name, parameter] of Object.entries(spec.parameters)) {
|
|
147
|
+
properties[name] = {
|
|
148
|
+
type: parameter.type === 'number' ? 'number'
|
|
149
|
+
: parameter.type === 'boolean' ? 'boolean'
|
|
150
|
+
: parameter.type === 'array' ? 'array'
|
|
151
|
+
: parameter.type === 'object' ? 'object'
|
|
152
|
+
: 'string',
|
|
153
|
+
description: parameter.description,
|
|
154
|
+
};
|
|
155
|
+
if (parameter.required === true)
|
|
156
|
+
required.push(name);
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
name: spec.name,
|
|
160
|
+
description: spec.description,
|
|
161
|
+
parameters: { type: 'object', properties, ...required.length > 0 ? { required } : {} },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Every registered DSH tool arbor does not already have, as arbor external
|
|
166
|
+
* tools.
|
|
167
|
+
*
|
|
168
|
+
* The specs are host-level and session-free: the same declarations reach the
|
|
169
|
+
* model whichever session's window is running, and each call executes as the
|
|
170
|
+
* session whose turn is running it. With no initiating agent — a call outside
|
|
171
|
+
* any turn — DSH's own pipeline degrades the execution to a deny, which is the
|
|
172
|
+
* correct fail-closed answer.
|
|
173
|
+
*
|
|
174
|
+
* The execution carries no log position, so DSH's pipeline does not write the
|
|
175
|
+
* `tool/call` + `tool/result` pair: the driver's bridge writes one pair for
|
|
176
|
+
* every call the window runs, arbor's own tools included, because the session
|
|
177
|
+
* log is the history the next model request is derived from.
|
|
178
|
+
*/
|
|
179
|
+
export function dshExternalTools(options) {
|
|
180
|
+
const { tools, agents } = options;
|
|
181
|
+
const replaced = new Set(DSH_TOOLS_ARBOR_REPLACES);
|
|
182
|
+
return tools.schemas()
|
|
183
|
+
.filter(schema => !replaced.has(schema.name))
|
|
184
|
+
.map(schema => ({
|
|
185
|
+
name: schema.name,
|
|
186
|
+
description: schema.description,
|
|
187
|
+
// DSH's own JSON Schema, carried verbatim: what the model reads is what
|
|
188
|
+
// DSH declared, not a summary of it.
|
|
189
|
+
schema: schema.parameters,
|
|
190
|
+
execute: async (request) => {
|
|
191
|
+
const agent = agents.currentInitiator();
|
|
192
|
+
const live = agent;
|
|
193
|
+
const signal = live?.currentSignal?.() ?? new AbortController().signal;
|
|
194
|
+
const call = {
|
|
195
|
+
type: 'tool-call',
|
|
196
|
+
id: request.callId,
|
|
197
|
+
name: request.name,
|
|
198
|
+
arguments: JSON.stringify(request.args),
|
|
199
|
+
};
|
|
200
|
+
const batch = await tools.execute({
|
|
201
|
+
calls: [call],
|
|
202
|
+
...agent === undefined ? {} : { agent },
|
|
203
|
+
signal,
|
|
204
|
+
});
|
|
205
|
+
const settled = batch.results.find(result => result.callId === request.callId) ?? batch.results[0];
|
|
206
|
+
const result = settled?.result;
|
|
207
|
+
if (result === undefined) {
|
|
208
|
+
return { callId: request.callId, ok: false, content: `tool "${request.name}" produced no result`, tool: request.name };
|
|
209
|
+
}
|
|
210
|
+
const text = result.content.map(block => ('text' in block ? block.text : '')).join('');
|
|
211
|
+
return {
|
|
212
|
+
callId: request.callId,
|
|
213
|
+
ok: !result.isError,
|
|
214
|
+
content: text,
|
|
215
|
+
tool: request.name,
|
|
216
|
+
...result.isError ? { error: { rule: 'tool.failed', message: text } } : {},
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
}));
|
|
220
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@4fu/dsh-arbor-tools",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "2026.1110.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Tool bridge: DSH tools enter arbor as external tools with their own JSON Schema, the ones arbor replaces are restricted away, and calls run through ctx.tools.execute",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./index.d.ts",
|
|
9
|
+
"default": "./index.js"
|
|
10
|
+
},
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"index.d.ts",
|
|
15
|
+
"index.js"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@4fu/arbor-interfaces": "2026.1109.1"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@deepseek-ai/dsh-agent": "0.2.2-rc.1",
|
|
22
|
+
"@deepseek-ai/dsh-llm": "0.2.2-rc.1",
|
|
23
|
+
"@deepseek-ai/dsh-tools": "0.2.2-rc.1"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/4fuu/dsh-arbor.git",
|
|
29
|
+
"directory": "packages/arbor-tools"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=24.9.0",
|
|
33
|
+
"dsh": "0.2.2-rc.1"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"types": "./index.d.ts",
|
|
39
|
+
"main": "./index.js"
|
|
40
|
+
}
|