@4fu/dsh-arbor-workspace 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 +20 -0
- package/index.js +20 -0
- package/package.json +44 -4
- package/projects.d.ts +280 -0
- package/projects.js +391 -0
- package/section.d.ts +97 -0
- package/section.js +188 -0
- package/workspace.d.ts +117 -0
- package/workspace.js +130 -0
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,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @4fu/dsh-arbor-workspace — arbor's own workspace over the shared folder, and
|
|
3
|
+
* the DSH project bridge.
|
|
4
|
+
*
|
|
5
|
+
* For arbor sessions arbor's tools run on the host the way they do in standalone
|
|
6
|
+
* arbor, so the workspace is arbor's own: ovnfs when its daemon starts, the
|
|
7
|
+
* plain-directory workspace otherwise, both rooted at the arbor shared folder
|
|
8
|
+
* under `DSH_HOME/arbor`. There is no DSH sandbox seam here, no `sandbox_escalate`
|
|
9
|
+
* and no `fileToolsSandboxed`; the `<workspace>` section and the `workspace.*`
|
|
10
|
+
* module functions are arbor's own.
|
|
11
|
+
*
|
|
12
|
+
* `DshProjectBridge` is the other half of arbor taking over DSH's project
|
|
13
|
+
* management: it makes the shared folder's project subfolders the DSH project
|
|
14
|
+
* list (`ctx.workspaceRegistry`), and it records an existing directory with its
|
|
15
|
+
* own path as an external pointer (name and absolute path, no copy) that
|
|
16
|
+
* arbor's prompt names, rather than adopting it into the shared folder.
|
|
17
|
+
*/
|
|
18
|
+
export { openArborWorkspace, arborSharedFolder, withArborWorkspace, type ArborWorkspace, type ArborWorkspaceOptions, } from './workspace.js';
|
|
19
|
+
export { withPointerSection, POINTER_BLOCKS, POINTER_TEXT, type PointerSource, } from './section.js';
|
|
20
|
+
export { DshProjectBridge, type DshProject, type DshProjectBridgeOptions, type ExternalPointer, type ExternalProject, } from './projects.js';
|
package/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @4fu/dsh-arbor-workspace — arbor's own workspace over the shared folder, and
|
|
3
|
+
* the DSH project bridge.
|
|
4
|
+
*
|
|
5
|
+
* For arbor sessions arbor's tools run on the host the way they do in standalone
|
|
6
|
+
* arbor, so the workspace is arbor's own: ovnfs when its daemon starts, the
|
|
7
|
+
* plain-directory workspace otherwise, both rooted at the arbor shared folder
|
|
8
|
+
* under `DSH_HOME/arbor`. There is no DSH sandbox seam here, no `sandbox_escalate`
|
|
9
|
+
* and no `fileToolsSandboxed`; the `<workspace>` section and the `workspace.*`
|
|
10
|
+
* module functions are arbor's own.
|
|
11
|
+
*
|
|
12
|
+
* `DshProjectBridge` is the other half of arbor taking over DSH's project
|
|
13
|
+
* management: it makes the shared folder's project subfolders the DSH project
|
|
14
|
+
* list (`ctx.workspaceRegistry`), and it records an existing directory with its
|
|
15
|
+
* own path as an external pointer (name and absolute path, no copy) that
|
|
16
|
+
* arbor's prompt names, rather than adopting it into the shared folder.
|
|
17
|
+
*/
|
|
18
|
+
export { openArborWorkspace, arborSharedFolder, withArborWorkspace, } from "./workspace.js";
|
|
19
|
+
export { withPointerSection, POINTER_BLOCKS, POINTER_TEXT, } from "./section.js";
|
|
20
|
+
export { DshProjectBridge, } from "./projects.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@4fu/dsh-arbor-workspace",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "2026.1110.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Arbor's own workspace over the shared folder (ovnfs, or the plain-directory fallback), with no DSH sandbox, and the bridge that makes the shared folder's projects the DSH project list",
|
|
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
|
+
"projects.d.ts",
|
|
17
|
+
"projects.js",
|
|
18
|
+
"section.d.ts",
|
|
19
|
+
"section.js",
|
|
20
|
+
"workspace.d.ts",
|
|
21
|
+
"workspace.js"
|
|
22
|
+
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@4fu/arbor-core": "2026.1109.1",
|
|
25
|
+
"@4fu/arbor-interfaces": "2026.1109.1"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@deepseek-ai/dsh-session": "0.2.2-rc.1",
|
|
29
|
+
"@deepseek-ai/dsh-workspace": "0.2.2-rc.1"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/4fuu/dsh-arbor.git",
|
|
35
|
+
"directory": "packages/arbor-workspace"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=24.9.0",
|
|
39
|
+
"dsh": "0.2.2-rc.1"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"types": "./index.d.ts",
|
|
45
|
+
"main": "./index.js"
|
|
46
|
+
}
|
package/projects.d.ts
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DSH project list, backed by arbor's shared folder.
|
|
3
|
+
*
|
|
4
|
+
* DSH models a project as a `Workspace` in its durable `WorkspaceRegistry`
|
|
5
|
+
* (`ctx.workspaceRegistry`, `@deepseek-ai/dsh-workspace`): a stable id over a
|
|
6
|
+
* canonical host directory, a title and an ordered session account. The web
|
|
7
|
+
* client's project list, project picker and creation all read that registry, so
|
|
8
|
+
* making arbor's projects appear there is what puts them in the UI — no client
|
|
9
|
+
* patch is needed for the list itself.
|
|
10
|
+
*
|
|
11
|
+
* Arbor's decision, which reverses B3: for arbor sessions arbor takes over DSH's
|
|
12
|
+
* project management, and a DSH project is a project subfolder of arbor's shared
|
|
13
|
+
* folder (`DSH_HOME/arbor/shared`, ovnfs's source). This bridge keeps that true:
|
|
14
|
+
*
|
|
15
|
+
* - {@link DshProjectBridge.sync} lists the shared folder's subfolders and
|
|
16
|
+
* registers each as a DSH `Workspace`, idempotently (the same canonical path
|
|
17
|
+
* reuses the existing record). It runs at host start and again whenever the
|
|
18
|
+
* shared folder changes, so a project a window creates with `mkdir` shows up
|
|
19
|
+
* without a restart.
|
|
20
|
+
* - {@link DshProjectBridge.resolveProject} answers where an arbor session
|
|
21
|
+
* works: a requested directory that is already inside the shared folder is a
|
|
22
|
+
* project; the shared folder itself means "no specific project yet"; and a
|
|
23
|
+
* directory outside the shared folder is answered as itself, so the session's
|
|
24
|
+
* cwd is the directory the user picked for it. That directory is also
|
|
25
|
+
* recorded as a {@link ExternalPointer} (name and absolute path) when the user
|
|
26
|
+
* added it through DSH's own add-workspace action — never copied, linked or
|
|
27
|
+
* adopted, because the owner's decision is no copy: arbor's tools run on the
|
|
28
|
+
* host, so the model can work in the user's own directory directly, and the
|
|
29
|
+
* pointer line in arbor's prompt says so. Arbor's own window still works in
|
|
30
|
+
* arbor's own directory either way; the pointer is what names the user's
|
|
31
|
+
* files. The pointer is recorded for the session that asked for it — see
|
|
32
|
+
* {@link addedDirectories} for which directory counts as asked for — so a
|
|
33
|
+
* session that never asked never has the line in its prompt.
|
|
34
|
+
*
|
|
35
|
+
* The registry is process-global, so this reflects arbor's projects for every
|
|
36
|
+
* reader; DSH offers no per-session view of the workspace list (see
|
|
37
|
+
* `docs/b3b-projects-and-sandbox.md`), so hiding arbor's projects from an
|
|
38
|
+
* ordinary session's picker is not something this bridge can do. Ordinary
|
|
39
|
+
* sessions keep their own cwd and sandbox on the embedded AgentLoop regardless —
|
|
40
|
+
* this bridge never touches a session's own directory or its log.
|
|
41
|
+
*/
|
|
42
|
+
import type { WorkspaceId, WorkspaceRegistry } from '@deepseek-ai/dsh-workspace';
|
|
43
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
44
|
+
/** One project as this bridge reports it. */
|
|
45
|
+
export interface DshProject {
|
|
46
|
+
readonly id: WorkspaceId;
|
|
47
|
+
readonly path: string;
|
|
48
|
+
readonly title: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* One external directory recorded as a pointer.
|
|
52
|
+
*
|
|
53
|
+
* The owner's decision for an existing DSH project with its own path is no copy,
|
|
54
|
+
* no mount and no adoption: the session goes to arbor's own directory (the
|
|
55
|
+
* shared folder) and the user's original directory is recorded as a pointer, so
|
|
56
|
+
* arbor's prompt can name it and the model can work in it directly (arbor's
|
|
57
|
+
* tools run on the host, so the absolute path is reachable). A pointer is never
|
|
58
|
+
* registered as a project arbor works in — it is a fact in the prompt, not a
|
|
59
|
+
* checkout.
|
|
60
|
+
*/
|
|
61
|
+
export interface ExternalPointer {
|
|
62
|
+
/** The directory's basename, the name the prompt shows. */
|
|
63
|
+
readonly name: string;
|
|
64
|
+
/** The absolute path the prompt names, where the user's own files are. */
|
|
65
|
+
readonly path: string;
|
|
66
|
+
}
|
|
67
|
+
/** One project of the shared folder: the one a session works in. */
|
|
68
|
+
export interface ExternalProject {
|
|
69
|
+
/** The project folder's own name, the name the DSH project list shows. */
|
|
70
|
+
readonly name: string;
|
|
71
|
+
/** The project's absolute path, a subfolder of the shared folder. */
|
|
72
|
+
readonly path: string;
|
|
73
|
+
}
|
|
74
|
+
/** What this bridge needs. */
|
|
75
|
+
export interface DshProjectBridgeOptions {
|
|
76
|
+
/**
|
|
77
|
+
* DSH's durable workspace registry, for a deployment that has one when this
|
|
78
|
+
* bridge is built. A deployment whose registry activates later — the web
|
|
79
|
+
* profile's activates after the arbor host — attaches it with
|
|
80
|
+
* {@link DshProjectBridge.attach} instead; absent throughout leaves the bridge
|
|
81
|
+
* inert.
|
|
82
|
+
*/
|
|
83
|
+
readonly registry?: WorkspaceRegistry | undefined;
|
|
84
|
+
/** The arbor shared folder: the projects' home and ovnfs's source. */
|
|
85
|
+
readonly sharedFolder: string;
|
|
86
|
+
/** Called with a non-fatal note, such as an adopt or sync failure. */
|
|
87
|
+
readonly warn?: (message: string) => void;
|
|
88
|
+
}
|
|
89
|
+
export declare class DshProjectBridge {
|
|
90
|
+
private registry;
|
|
91
|
+
private readonly sharedFolder;
|
|
92
|
+
private readonly warn;
|
|
93
|
+
private watcher;
|
|
94
|
+
private debounce;
|
|
95
|
+
/**
|
|
96
|
+
* Recorded external directories, per session: name -> absolute path. Never
|
|
97
|
+
* projects. A session's set is what that session's `<workspace>` section
|
|
98
|
+
* names, so a directory one session's user added is invisible to a session
|
|
99
|
+
* that never asked for it.
|
|
100
|
+
*/
|
|
101
|
+
private readonly pointers;
|
|
102
|
+
/**
|
|
103
|
+
* Canonical paths of directories the user added through DSH's own
|
|
104
|
+
* add-workspace action, recorded by the {@link watchCreates} hook on the
|
|
105
|
+
* registry. A pointer is recorded for an arbor session only when the session
|
|
106
|
+
* names one of these: a directory the user deliberately added is a directory
|
|
107
|
+
* the user means arbor to name, while a cwd a session merely inherited (DSH's
|
|
108
|
+
* first-use default workspace, the most recent workspace) is not.
|
|
109
|
+
*/
|
|
110
|
+
private readonly added;
|
|
111
|
+
constructor(options: DshProjectBridgeOptions);
|
|
112
|
+
/**
|
|
113
|
+
* Attach DSH's durable workspace registry, register the shared folder's
|
|
114
|
+
* projects in it, and keep the list live from then on. Returns a disposer.
|
|
115
|
+
*
|
|
116
|
+
* The registry is a web-profile service that activates after the arbor host
|
|
117
|
+
* does, so a deployment cannot read it while the host is being built; this is
|
|
118
|
+
* the seam that connects the two, the same one `@4fu/dsh-arbor-preset` uses
|
|
119
|
+
* for the preset roster.
|
|
120
|
+
*/
|
|
121
|
+
attach(registry: WorkspaceRegistry): () => void;
|
|
122
|
+
/**
|
|
123
|
+
* Watch `WorkspaceRegistry.create` and remember every directory it registers
|
|
124
|
+
* outside the shared folder.
|
|
125
|
+
*
|
|
126
|
+
* This is the seam that makes DSH's own add-workspace action record a pointer
|
|
127
|
+
* for an arbor session with no new verb and no client change: the client's
|
|
128
|
+
* add-workspace action calls the `workspace/create` remote verb
|
|
129
|
+
* (`@deepseek-ai/dsh-api-workspace-controller`, `create`), which is the one
|
|
130
|
+
* caller of `WorkspaceRegistry.create` in a DSH installation besides this
|
|
131
|
+
* bridge's own {@link sync} — and `sync` only ever names project subfolders of
|
|
132
|
+
* the shared folder, which the outside-shared-folder rule already excludes.
|
|
133
|
+
* DSH's first-use default workspace is registered by
|
|
134
|
+
* `WorkspaceRegistry.initializeDefault`, a different method that never reaches
|
|
135
|
+
* `create`, so an inherited cwd is never mistaken for an added directory.
|
|
136
|
+
*
|
|
137
|
+
* The hook is a wrapper on the registry instance this bridge was handed,
|
|
138
|
+
* restored never: the instance lives as long as the process, and the wrapper
|
|
139
|
+
* delegates to the original for every call.
|
|
140
|
+
*/
|
|
141
|
+
private watchCreates;
|
|
142
|
+
/**
|
|
143
|
+
* Every canonical path the user added through DSH's add-workspace action, in
|
|
144
|
+
* add order. A directory is added once and stays added: it is a fact about
|
|
145
|
+
* what the user put in the project list, not about the session that asked.
|
|
146
|
+
*/
|
|
147
|
+
addedDirectories(): readonly string[];
|
|
148
|
+
/** Whether the user added this directory through DSH's add-workspace action. */
|
|
149
|
+
isAddedDirectory(path: string): boolean;
|
|
150
|
+
/** Whether a requested directory already names the shared folder itself. */
|
|
151
|
+
isSharedFolder(path: string): boolean;
|
|
152
|
+
/** Whether a requested directory already sits inside the shared folder. */
|
|
153
|
+
insideSharedFolder(path: string): boolean;
|
|
154
|
+
/**
|
|
155
|
+
* The project a directory names, or undefined when it names none.
|
|
156
|
+
*
|
|
157
|
+
* A project is a subfolder of the shared folder, which is the one place a
|
|
158
|
+
* project lives: the shared folder itself is "no specific project yet", and a
|
|
159
|
+
* directory outside it is not a project at all (it is a pointer, when the user
|
|
160
|
+
* added it). The name is the folder's own, so the prompt, the DSH project list
|
|
161
|
+
* and the checkout a window opens all name the same thing.
|
|
162
|
+
*/
|
|
163
|
+
projectOf(path: string | undefined): ExternalProject | undefined;
|
|
164
|
+
/**
|
|
165
|
+
* Where one arbor session works: the directory its caller named.
|
|
166
|
+
*
|
|
167
|
+
* The shared folder itself (or nothing) is answered as-is — a session works in
|
|
168
|
+
* the shared folder and opens a checkout of whichever project it needs, which
|
|
169
|
+
* is what "an arbor session is not bound to one cwd" means. A path already
|
|
170
|
+
* inside the shared folder is a project and is answered as-is, after a sync so
|
|
171
|
+
* a project a window just made is in DSH's list.
|
|
172
|
+
*
|
|
173
|
+
* A directory outside the shared folder is answered as-is too. That reverses
|
|
174
|
+
* the b3c rule, which moved such a session into the shared folder, and the
|
|
175
|
+
* reason is DSH's own membership rule: a workspace's `sessionIds` are filtered
|
|
176
|
+
* by the session's stored cwd (`@deepseek-ai/dsh-workspace`,
|
|
177
|
+
* `WorkspaceEntity.sessionIds` → `candidateMatches` →
|
|
178
|
+
* `sessionPaths.get(id) === path`), and `Workspace.attachSession` refuses a
|
|
179
|
+
* session whose cwd is not the workspace's path. So a session whose cwd was
|
|
180
|
+
* moved into the shared folder belongs to no workspace the user can see, and
|
|
181
|
+
* the only way to give it one was to register the shared folder itself — a
|
|
182
|
+
* row that names arbor's internal folder rather than anything the user
|
|
183
|
+
* picked. Answering the requested directory as-is makes the DSH session, its
|
|
184
|
+
* cwd display, the project picker and the sidebar row name one directory:
|
|
185
|
+
* the project the user picked, or the directory the user added.
|
|
186
|
+
*
|
|
187
|
+
* What does not change is where the *window* works: arbor's own directory
|
|
188
|
+
* under `DSH_HOME/arbor`, reading the shared folder through ovnfs and opening
|
|
189
|
+
* checkouts in it. The session's cwd is a DSH-side fact — the cwd display, the
|
|
190
|
+
* project line, the workspace membership — and arbor's prompt says where the
|
|
191
|
+
* window works in its own words, so the two never contradict each other.
|
|
192
|
+
*
|
|
193
|
+
* Recording an outside directory as an {@link ExternalPointer} is NOT done
|
|
194
|
+
* here. This runs for every arbor session, whatever directory its caller
|
|
195
|
+
* happened to name, and a pointer is a deliberate record of a directory the
|
|
196
|
+
* user means arbor to name — a session's requested cwd is not that. The
|
|
197
|
+
* deliberate callers are {@link addPointer}: the driver, for a session that
|
|
198
|
+
* became an arbor session while naming a directory the user had added through
|
|
199
|
+
* DSH's own add-workspace action ({@link addedDirectories}), and the model.
|
|
200
|
+
*/
|
|
201
|
+
resolveProject(requested: string | undefined): Promise<string>;
|
|
202
|
+
/**
|
|
203
|
+
* Record an existing directory as an external pointer for one session: no
|
|
204
|
+
* copy, no link, no adoption, and never a registered project. The caller that
|
|
205
|
+
* means it — the driver, for a session that became an arbor session while
|
|
206
|
+
* naming a directory the user added, or the model — names the directory and
|
|
207
|
+
* the session it belongs to, and its name and absolute path are recorded so
|
|
208
|
+
* arbor's prompt can name where the user's own files are; arbor's tools run
|
|
209
|
+
* on the host, so the model reaches the path directly and a change there
|
|
210
|
+
* lands on the user's files with no checkout and no apply. A second call for
|
|
211
|
+
* the same name overwrites the path, so pointing a name at a new directory is
|
|
212
|
+
* one edit.
|
|
213
|
+
*/
|
|
214
|
+
addPointer(sessionId: SessionId, externalPath: string): ExternalPointer;
|
|
215
|
+
/** Drop a pointer by name for one session; a no-op when the name is not recorded. */
|
|
216
|
+
removePointer(sessionId: SessionId, name: string): void;
|
|
217
|
+
/**
|
|
218
|
+
* Every pointer one session recorded whose directory still exists, true at the
|
|
219
|
+
* moment it is read: a pointer whose directory is gone is dropped here, so the
|
|
220
|
+
* prompt never names a path that no longer exists. Sorted by name for a stable
|
|
221
|
+
* line. An unknown session has none, which is the answer a window whose
|
|
222
|
+
* session cannot be named gives — never another session's list.
|
|
223
|
+
*/
|
|
224
|
+
listPointers(sessionId: SessionId | undefined): Promise<readonly ExternalPointer[]>;
|
|
225
|
+
/**
|
|
226
|
+
* A revision that moves whenever any session's live pointer set moves, so a
|
|
227
|
+
* running window's section is re-rendered — and handed a delta of only the
|
|
228
|
+
* changed line — the moment the set changes. The host asks for it once per
|
|
229
|
+
* step, so the existence check lives here: reading it drops a pointer whose
|
|
230
|
+
* directory has gone, which moves the revision, which drops the line from the
|
|
231
|
+
* prompt. It is a digest of every session's live set rather than a counter, so
|
|
232
|
+
* a pointer removed and re-added at the same path costs nothing.
|
|
233
|
+
*/
|
|
234
|
+
pointerRevision(): Promise<string>;
|
|
235
|
+
/**
|
|
236
|
+
* Make one arbor session a member of the workspace that owns the directory it
|
|
237
|
+
* works in, so DSH's own hero composer has a workspace to name and the sidebar
|
|
238
|
+
* shows the session where the user picked it.
|
|
239
|
+
*
|
|
240
|
+
* DSH answers "which workspace is this session in" from the workspace whose
|
|
241
|
+
* member list names it, and filters a workspace's members by the session's
|
|
242
|
+
* stored cwd (`@deepseek-ai/dsh-workspace`, `WorkspaceEntity.sessionIds` ->
|
|
243
|
+
* `candidateMatches`), so an arbor session is only ever a member of a
|
|
244
|
+
* workspace whose path is the directory it works in. Without this, a session
|
|
245
|
+
* the driver created or re-routed under arbor belongs to no workspace at all,
|
|
246
|
+
* and the hero composer answers `chipTitle === undefined` and renders inert
|
|
247
|
+
* (`@deepseek-ai/dsh-client-ui-conversation`, `lib/client.js`:
|
|
248
|
+
* `inert = sessionId === void 0 || hero && chipTitle === void 0`) — the dead
|
|
249
|
+
* composer the outside-directory add used to leave behind.
|
|
250
|
+
*
|
|
251
|
+
* **The shared folder is never registered.** It is arbor's own internal
|
|
252
|
+
* directory, not a project the user made, so a DSH workspace row for it would
|
|
253
|
+
* name something the user never picked. A session whose cwd is the shared
|
|
254
|
+
* folder itself therefore belongs to no workspace: nothing in the web profile
|
|
255
|
+
* creates one, because every session there is created in a workspace
|
|
256
|
+
* (`session/create` takes `workspaceId` or `cwd`, and the picker and the
|
|
257
|
+
* add-workspace flow both name a workspace).
|
|
258
|
+
*
|
|
259
|
+
* A project subfolder of the shared folder is registered the same way
|
|
260
|
+
* {@link sync} registers one, so a project a window made with `mkdir` is a
|
|
261
|
+
* workspace the session can belong to. A directory outside the shared folder
|
|
262
|
+
* is only attached to when DSH's list already has it — which is the case for
|
|
263
|
+
* every directory a user added through DSH's own add-workspace action, since
|
|
264
|
+
* that action registers it before it is picked. Registering one here would add
|
|
265
|
+
* a project-list row for a directory nobody added.
|
|
266
|
+
*/
|
|
267
|
+
attachWorkspace(sessionId: SessionId, cwd: string): Promise<void>;
|
|
268
|
+
/** Every project subfolder of the shared folder, registered in DSH's registry. */
|
|
269
|
+
sync(): Promise<readonly DshProject[]>;
|
|
270
|
+
/** The projects currently registered, as this bridge last synced the disk. */
|
|
271
|
+
list(): Promise<readonly DshProject[]>;
|
|
272
|
+
/**
|
|
273
|
+
* Watch the shared folder and re-sync when a project appears or disappears, so
|
|
274
|
+
* a project a window creates with `mkdir` reaches the DSH project list without
|
|
275
|
+
* a restart. Returns a disposer.
|
|
276
|
+
*/
|
|
277
|
+
start(): () => void;
|
|
278
|
+
/** Stop watching. */
|
|
279
|
+
stop(): void;
|
|
280
|
+
}
|