dsh-multi-folder 0.1.1 → 0.1.2
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/README.md +3 -2
- package/README.zh.md +3 -2
- package/docs/design.md +16 -2
- package/docs/upstream-hero-slot.md +12 -5
- package/lib/client.js +102 -29
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -13,7 +13,8 @@ A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin bun
|
|
|
13
13
|
- Under **Workspace Write** mode the agent gains the **same read / write / edit / execute permissions** on the configured secondary directories as on the primary workspace — enforced by re-rooting the session's own sandbox policy, so every mode keeps its semantics (`read-only` still denies, `workspace-write` allows, `danger-full-access` allows).
|
|
14
14
|
- The directory list is **injected into the system prompt** and re-rendered per session assembly.
|
|
15
15
|
- Configuration changes notify the agent through a **non-interrupting message queue** — delivered at the next message boundary (user send or tool-call end), and **only when the directory set actually changed**.
|
|
16
|
-
- Configurable **before the session starts**: the session-creation page (new-session screen) offers a
|
|
16
|
+
- Configurable **before the session starts**: the session-creation page (new-session screen) offers a Multi-folder entry (「多工作目录」 in the Chinese UI) that reads and edits the same per-workspace configuration through a **sessionless remote API** (`multiFolder/*` endpoints) — no session id required.
|
|
17
|
+
- **Localized UI.** The button, panel, and creation-page entries follow the DSH locale (the browser language or the Language setting in Settings): "Multi-folder" in English, 「多工作目录」 in Chinese.
|
|
17
18
|
- **No new tools.** Everything is a framework-level change (tool-pipeline interception) plus a UI-level change (a session-scoped header entry).
|
|
18
19
|
|
|
19
20
|
## Requirements
|
|
@@ -33,7 +34,7 @@ Then **restart the DSH backend** (host composition loads at process start) and *
|
|
|
33
34
|
|
|
34
35
|
## Usage
|
|
35
36
|
|
|
36
|
-
A
|
|
37
|
+
A Multi-folder button (「多工作目录」 in the Chinese UI) appears in the session header, and a second entry appears on the **session-creation page** (fixed launcher in the bottom-right corner while the new-session screen is shown; an inline chip beside the workspace picker once the upstream `conversation.hero.workspaceExtras` slot is available). The panel lets you:
|
|
37
38
|
|
|
38
39
|
| Action | Behavior |
|
|
39
40
|
| ------ | -------- |
|
package/README.zh.md
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
- 在 **Workspace Write** 模式下,Agent 对配置的副工作目录拥有与主工作目录**同等的读取、写入、编辑与命令执行权限**——实现方式是重定向会话自身的沙箱策略根,因此每种模式语义都自然保持(`read-only` 依旧拒绝、`workspace-write` 放行、`danger-full-access` 放行);
|
|
14
14
|
- 目录列表**注入系统提示词**,每次组装按会话求值;
|
|
15
15
|
- 配置变更通过**不打断的消息队列**通知 Agent——在下一次消息边界(用户发送或工具调用结束)送达,且**仅在目录集合实际变化时**发送;
|
|
16
|
-
-
|
|
16
|
+
- **会话开始前即可配置**:会话创建页(新会话界面)提供「多工作目录」入口(英文界面显示 "Multi-folder"),通过**无会话远程 API**(`multiFolder/*` 端点)读写同一份 per-workspace 配置——无需 session id;
|
|
17
|
+
- **界面本地化**:按钮、面板与创建页入口跟随 DSH 的语言设置(浏览器语言或设置中的 Language 选项):英文界面显示 "Multi-folder",中文界面显示「多工作目录」。
|
|
17
18
|
- **不新增任何工具**:改动全部位于框架级(工具流水线拦截)与 UI 级(会话级头部入口)。
|
|
18
19
|
|
|
19
20
|
## 环境要求
|
|
@@ -33,7 +34,7 @@ dsh plugin --profile web add dsh-multi-folder
|
|
|
33
34
|
|
|
34
35
|
## 使用
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
会话头部出现「多工作目录」按钮(英文界面显示 "Multi-folder");**会话创建页**也有入口(新会话界面右下角的浮动按钮;当上游 DSH 声明 `conversation.hero.workspaceExtras` 插槽后,还会在工作区选择器旁显示内联 chip)。打开面板即可:
|
|
37
38
|
|
|
38
39
|
| 操作 | 行为 |
|
|
39
40
|
| ---- | ---- |
|
package/docs/design.md
CHANGED
|
@@ -145,14 +145,28 @@ window.__ModuleLoader__.load({
|
|
|
145
145
|
})
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
- `inject: ['remote', 'remote.commands', 'slots', 'workspaces', 'connection', 'sessions']`; the package's
|
|
148
|
+
- `inject: ['remote', 'remote.commands', 'slots', 'workspaces', 'connection', 'sessions', 'locale']`; the package's
|
|
149
149
|
`dsh.client.inject` lists the packages providing them
|
|
150
150
|
(`@deepseek-ai/dsh-api-gateway`, `@deepseek-ai/dsh-api-remotes`,
|
|
151
|
-
`@deepseek-ai/dsh-client-connection`, `@deepseek-ai/dsh-client-
|
|
151
|
+
`@deepseek-ai/dsh-client-connection`, `@deepseek-ai/dsh-client-locale`,
|
|
152
|
+
`@deepseek-ai/dsh-client-runtime`).
|
|
152
153
|
- UI registrations: `conversation.session.header.actions` (session-scoped button),
|
|
153
154
|
`shell.overlay` panel, `shell.overlay` hero launcher (root-scoped, fixed
|
|
154
155
|
position), and `conversation.hero.workspaceExtras` (upstream slot; see
|
|
155
156
|
below). One module-level store is shared by all of them.
|
|
157
|
+
- **Localization (zh / en).** All client copy goes through
|
|
158
|
+
`@deepseek-ai/dsh-client-locale` (always composed by the standard web
|
|
159
|
+
profile). The bundle registers a `multi-folder` dictionary namespace with
|
|
160
|
+
`ctx.effect(() => locale.register(NS, { zh, en }))` — the locale service
|
|
161
|
+
enforces bilingual balance, and the effect ties the dictionaries to the
|
|
162
|
+
plugin fiber. Every slot registration declares `locale: 'multi-folder'`,
|
|
163
|
+
so the renderer synthesizes the `t` seat on component props and
|
|
164
|
+
re-renders mounted outlets on locale switch; list-entry `label`s are
|
|
165
|
+
thunks (`() => t('label')`) that `resolveSlotLabel` re-evaluates per read,
|
|
166
|
+
so registration-time text follows the active locale without
|
|
167
|
+
re-registering. The active locale is the browser language or the user's
|
|
168
|
+
Language preference in Settings; the English UI reads "Multi-folder", the
|
|
169
|
+
Chinese UI keeps 「多工作目录」.
|
|
156
170
|
- Host communication, two channels:
|
|
157
171
|
- session mode: `ctx.remote.commands.execute(sessionId, line)`. The return
|
|
158
172
|
value is the RPC envelope `{ ok, value }` where `value` is the
|
|
@@ -78,17 +78,23 @@ above is only authorized once it is listed):
|
|
|
78
78
|
|
|
79
79
|
## 4. Plugin side (already implemented)
|
|
80
80
|
|
|
81
|
-
`lib/client.js` registers into the slot the moment it is declared
|
|
81
|
+
`lib/client.js` registers into the slot the moment it is declared. The entry
|
|
82
|
+
declares the plugin's `multi-folder` locale namespace, so the chip renders
|
|
83
|
+
through the framework `t` seat and its `label` is a thunk that follows the
|
|
84
|
+
active locale (English: "Multi-folder", Chinese: 「多工作目录」):
|
|
82
85
|
|
|
83
86
|
```js
|
|
84
87
|
slots.inject('conversation.hero.workspaceExtras', function () {
|
|
85
88
|
return slots.register(
|
|
86
|
-
{ name: 'conversation.hero.workspaceExtras', id: 'multi-folder', order: 30, label: '
|
|
89
|
+
{ name: 'conversation.hero.workspaceExtras', id: 'multi-folder', order: 30, label: () => t('label'), locale: NS },
|
|
87
90
|
function (props) { return React.createElement(HeroChip, props); },
|
|
88
91
|
);
|
|
89
92
|
});
|
|
90
93
|
```
|
|
91
94
|
|
|
95
|
+
(`NS` is the plugin's `multi-folder` namespace and `t` its bound translator —
|
|
96
|
+
see the localization section of [design.md](design.md).)
|
|
97
|
+
|
|
92
98
|
`HeroChip` prefers the owner-supplied `props.workspacePath` and falls back to
|
|
93
99
|
the store-derived hero workspace. It opens the same overlay panel in workspace
|
|
94
100
|
mode, which reads and writes the configuration through the sessionless
|
|
@@ -115,6 +121,7 @@ bundle; the same change there is three touchpoints:
|
|
|
115
121
|
- Unit: the plugin's `test/smoke-client.mjs` registers and drives `HeroChip`
|
|
116
122
|
with a mock owner share (`workspacePath`), asserting the panel opens in
|
|
117
123
|
workspace mode and reuses the per-workspace cache.
|
|
118
|
-
- Manual: with the patched build, the
|
|
119
|
-
workspace row between the preset chip and
|
|
120
|
-
the workspace's secondary directories
|
|
124
|
+
- Manual: with the patched build, the Multi-folder chip (「多工作目录」 in the
|
|
125
|
+
Chinese UI) renders in the hero workspace row between the preset chip and
|
|
126
|
+
the composer; clicking it lists the workspace's secondary directories
|
|
127
|
+
before any message is sent.
|
package/lib/client.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-multi-folder — client half (hand-written factory bundle, no build step).
|
|
3
3
|
*
|
|
4
|
-
* Session-scoped UI: a "
|
|
5
|
-
* (`conversation.session.header.actions`, scope:
|
|
6
|
-
* `shell.overlay` listing the project's
|
|
4
|
+
* Session-scoped UI: a localized "Multi-folder" (「多工作目录」) button in the
|
|
5
|
+
* conversation session header (`conversation.session.header.actions`, scope:
|
|
6
|
+
* session) that opens a panel in `shell.overlay` listing the project's
|
|
7
|
+
* secondary working directories. All copy goes through the framework locale
|
|
8
|
+
* service (`@deepseek-ai/dsh-client-locale`) — see the "i18n" section below.
|
|
7
9
|
* Mutations go through the Host `/multi-folder` command via the Remote BFF
|
|
8
10
|
* (`ctx.remote.commands.execute(sessionId, line)`); the Host answers with a
|
|
9
11
|
* human-readable result carrying a `[MF:JSON]` line the panel parses for
|
|
@@ -62,9 +64,60 @@ window.__ModuleLoader__.load({
|
|
|
62
64
|
try { return JSON.parse(m[1]); } catch (_) { return null; }
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
// ------------------------------------------------------------- i18n
|
|
68
|
+
/** Locale namespace owned by this plugin. Dictionaries are registered
|
|
69
|
+
* with the `locale` service (`@deepseek-ai/dsh-client-locale`, always
|
|
70
|
+
* composed by the standard web profile), which enforces bilingual
|
|
71
|
+
* balance: both shipped locales (zh, en) must be registered together. */
|
|
72
|
+
var NS = 'multi-folder';
|
|
73
|
+
/** Simplified Chinese dictionary — the key-set source of truth. */
|
|
74
|
+
var zhDict = {
|
|
75
|
+
'label': '多工作目录',
|
|
76
|
+
'label.open': '多工作目录 ▾',
|
|
77
|
+
'label.heroLauncher': '多工作目录(新会话)',
|
|
78
|
+
'title.header': '多工作目录(副工作目录)',
|
|
79
|
+
'title.remove': '移除此副工作目录',
|
|
80
|
+
'title.close': '关闭',
|
|
81
|
+
'title.heroLauncher.hasWorkspace': '配置此项目的副工作目录(多工作目录)',
|
|
82
|
+
'title.heroLauncher.noWorkspace': '请先选择工作区,再配置多工作目录',
|
|
83
|
+
'title.heroChip': '配置此项目的副工作目录(多工作目录)',
|
|
84
|
+
'panel.title': '多工作目录(副工作目录)',
|
|
85
|
+
'panel.project': '项目:{path}',
|
|
86
|
+
'panel.noWorkspaceHint': '尚未选择工作区。请先在上方选择项目,再配置多工作目录。',
|
|
87
|
+
'panel.empty': '尚未配置副工作目录。',
|
|
88
|
+
'panel.pickWorkspaceHint': '选择工作区后可在此添加副工作目录。',
|
|
89
|
+
'panel.add': '+ 添加目录',
|
|
90
|
+
'panel.adding': '处理中…',
|
|
91
|
+
'panel.remove': '移除',
|
|
92
|
+
'panel.refresh': '刷新',
|
|
93
|
+
'panel.footnote': 'Agent 的主工作目录不变;在 Workspace Write 模式下,Agent 对上述目录拥有与主工作目录同等的读写与命令执行权限。配置变更会在下一条消息或工具调用结束时通知 Agent。',
|
|
94
|
+
};
|
|
95
|
+
/** English dictionary — checked complete against the zh key set. */
|
|
96
|
+
var enDict = {
|
|
97
|
+
'label': 'Multi-folder',
|
|
98
|
+
'label.open': 'Multi-folder ▾',
|
|
99
|
+
'label.heroLauncher': 'Multi-folder (new session)',
|
|
100
|
+
'title.header': 'Multi-folder (secondary working directories)',
|
|
101
|
+
'title.remove': 'Remove this secondary working directory',
|
|
102
|
+
'title.close': 'Close',
|
|
103
|
+
'title.heroLauncher.hasWorkspace': 'Configure secondary working directories for this project (Multi-folder)',
|
|
104
|
+
'title.heroLauncher.noWorkspace': 'Pick a workspace first, then configure Multi-folder',
|
|
105
|
+
'title.heroChip': 'Configure secondary working directories for this project (Multi-folder)',
|
|
106
|
+
'panel.title': 'Multi-folder (secondary working directories)',
|
|
107
|
+
'panel.project': 'Workspace: {path}',
|
|
108
|
+
'panel.noWorkspaceHint': 'No workspace selected yet. Pick a project above first, then configure secondary directories.',
|
|
109
|
+
'panel.empty': 'No secondary working directories configured yet.',
|
|
110
|
+
'panel.pickWorkspaceHint': 'Pick a workspace first — secondary directories can be added here afterwards.',
|
|
111
|
+
'panel.add': '+ Add directory',
|
|
112
|
+
'panel.adding': 'Working…',
|
|
113
|
+
'panel.remove': 'Remove',
|
|
114
|
+
'panel.refresh': 'Refresh',
|
|
115
|
+
'panel.footnote': "The agent's primary working directory stays unchanged; under Workspace Write mode the agent has the same read/write and command-execution rights on the listed directories as on the primary workspace. Configuration changes are announced at the next message or tool-call boundary.",
|
|
116
|
+
};
|
|
117
|
+
|
|
65
118
|
// ------------------------------------------------------------- plugin
|
|
66
119
|
var name = 'dsh-multi-folder';
|
|
67
|
-
var inject = ['remote', 'remote.commands', 'slots', 'workspaces', 'connection', 'sessions'];
|
|
120
|
+
var inject = ['remote', 'remote.commands', 'slots', 'workspaces', 'connection', 'sessions', 'locale'];
|
|
68
121
|
|
|
69
122
|
function apply(ctx) {
|
|
70
123
|
var slots = ctx.slots;
|
|
@@ -72,6 +125,22 @@ window.__ModuleLoader__.load({
|
|
|
72
125
|
var workspaces = ctx.workspaces;
|
|
73
126
|
var connection = ctx.connection;
|
|
74
127
|
var sessions = ctx.sessions;
|
|
128
|
+
var locale = ctx.locale;
|
|
129
|
+
|
|
130
|
+
/** Register this plugin's dictionaries for every shipped locale
|
|
131
|
+
* (bilingual balance is enforced by the locale service). The
|
|
132
|
+
* registration is an effect on this plugin's fiber, so unloading the
|
|
133
|
+
* plugin withdraws the dictionaries. */
|
|
134
|
+
ctx.effect(function () {
|
|
135
|
+
return locale.register(NS, { zh: zhDict, en: enDict });
|
|
136
|
+
}, 'dsh-multi-folder: client dictionaries');
|
|
137
|
+
|
|
138
|
+
/** Bound translator for slot labels: label thunks re-evaluate per read
|
|
139
|
+
* (`resolveSlotLabel`), so registration-time text follows the active
|
|
140
|
+
* locale without re-registering. Components themselves render through
|
|
141
|
+
* the `t` seat the renderer synthesizes from the declared
|
|
142
|
+
* `locale:` namespace (which also re-renders them on locale switch). */
|
|
143
|
+
var t = locale.bind(NS);
|
|
75
144
|
|
|
76
145
|
// -------------------------------------------------- sessionless RPC
|
|
77
146
|
/** Call one `multiFolder/*` endpoint over the shared /api channel.
|
|
@@ -232,6 +301,7 @@ window.__ModuleLoader__.load({
|
|
|
232
301
|
function HeaderButton(props) {
|
|
233
302
|
var store = useStore();
|
|
234
303
|
var sessionId = props.sessionId;
|
|
304
|
+
var t = props.t;
|
|
235
305
|
var open = store.open && store.sessionId === sessionId;
|
|
236
306
|
// Session switch: keep the panel in sync with the session this
|
|
237
307
|
// header belongs to. On a changed sessionId (or first mount) with
|
|
@@ -250,7 +320,7 @@ window.__ModuleLoader__.load({
|
|
|
250
320
|
'button',
|
|
251
321
|
{
|
|
252
322
|
type: 'button',
|
|
253
|
-
title: '
|
|
323
|
+
title: t('title.header'),
|
|
254
324
|
onClick: function () {
|
|
255
325
|
var current = getSnapshot();
|
|
256
326
|
var isOpen = current.open && current.sessionId === sessionId;
|
|
@@ -273,14 +343,15 @@ window.__ModuleLoader__.load({
|
|
|
273
343
|
cursor: 'pointer',
|
|
274
344
|
},
|
|
275
345
|
},
|
|
276
|
-
open ? '
|
|
346
|
+
open ? t('label.open') : t('label'),
|
|
277
347
|
);
|
|
278
348
|
return btn;
|
|
279
349
|
}
|
|
280
350
|
|
|
281
351
|
// Overlay panel ------------------------------------------------------
|
|
282
|
-
function Panel() {
|
|
352
|
+
function Panel(props) {
|
|
283
353
|
var store = useStore();
|
|
354
|
+
var t = props.t;
|
|
284
355
|
if (!store.open || !store.mode) return null;
|
|
285
356
|
var sessionMode = store.mode === 'session';
|
|
286
357
|
var usable = sessionMode || !!store.workspace;
|
|
@@ -311,7 +382,7 @@ window.__ModuleLoader__.load({
|
|
|
311
382
|
'button',
|
|
312
383
|
{
|
|
313
384
|
type: 'button',
|
|
314
|
-
title: '
|
|
385
|
+
title: t('title.remove'),
|
|
315
386
|
disabled: !usable,
|
|
316
387
|
onClick: function () {
|
|
317
388
|
if (sessionMode) {
|
|
@@ -322,7 +393,7 @@ window.__ModuleLoader__.load({
|
|
|
322
393
|
},
|
|
323
394
|
style: { padding: '2px 8px', borderRadius: 6, border: '1px solid transparent', background: 'transparent', color: 'var(--color-danger, #c62828)', cursor: 'pointer' },
|
|
324
395
|
},
|
|
325
|
-
'
|
|
396
|
+
t('panel.remove'),
|
|
326
397
|
),
|
|
327
398
|
);
|
|
328
399
|
});
|
|
@@ -348,12 +419,12 @@ window.__ModuleLoader__.load({
|
|
|
348
419
|
React.createElement(
|
|
349
420
|
'div',
|
|
350
421
|
{ style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 } },
|
|
351
|
-
React.createElement('div', { style: { fontWeight: 600, fontSize: 14 } }, '
|
|
422
|
+
React.createElement('div', { style: { fontWeight: 600, fontSize: 14 } }, t('panel.title')),
|
|
352
423
|
React.createElement(
|
|
353
424
|
'button',
|
|
354
425
|
{
|
|
355
426
|
type: 'button',
|
|
356
|
-
title: '
|
|
427
|
+
title: t('title.close'),
|
|
357
428
|
onClick: function () { patch({ open: false }); },
|
|
358
429
|
style: { padding: '2px 8px', borderRadius: 6, border: '1px solid transparent', background: 'transparent', cursor: 'pointer' },
|
|
359
430
|
},
|
|
@@ -364,18 +435,18 @@ window.__ModuleLoader__.load({
|
|
|
364
435
|
? React.createElement(
|
|
365
436
|
'div',
|
|
366
437
|
{ style: { marginBottom: 8, color: 'var(--color-text-muted, #6b6b6b)', fontSize: 12, wordBreak: 'break-all' } },
|
|
367
|
-
'
|
|
438
|
+
t('panel.project', { path: store.workspace }),
|
|
368
439
|
)
|
|
369
440
|
: (!sessionMode
|
|
370
441
|
? React.createElement(
|
|
371
442
|
'div',
|
|
372
443
|
{ style: { marginBottom: 8, color: 'var(--color-text-muted, #6b6b6b)' } },
|
|
373
|
-
'
|
|
444
|
+
t('panel.noWorkspaceHint'),
|
|
374
445
|
)
|
|
375
446
|
: null),
|
|
376
447
|
usable
|
|
377
|
-
? (rows.length > 0 ? rows : React.createElement('div', { style: { marginBottom: 8, color: 'var(--color-text-muted, #6b6b6b)' } }, '
|
|
378
|
-
: React.createElement('div', { style: { marginBottom: 8, color: 'var(--color-text-muted, #6b6b6b)' } }, '
|
|
448
|
+
? (rows.length > 0 ? rows : React.createElement('div', { style: { marginBottom: 8, color: 'var(--color-text-muted, #6b6b6b)' } }, t('panel.empty')))
|
|
449
|
+
: React.createElement('div', { style: { marginBottom: 8, color: 'var(--color-text-muted, #6b6b6b)' } }, t('panel.pickWorkspaceHint')),
|
|
379
450
|
store.error
|
|
380
451
|
? React.createElement('div', { style: { marginBottom: 8, color: 'var(--color-danger, #c62828)', whiteSpace: 'pre-wrap' } }, String(store.error))
|
|
381
452
|
: null,
|
|
@@ -399,7 +470,7 @@ window.__ModuleLoader__.load({
|
|
|
399
470
|
opacity: store.busy || !usable ? 0.6 : 1,
|
|
400
471
|
},
|
|
401
472
|
},
|
|
402
|
-
store.busy ? '
|
|
473
|
+
store.busy ? t('panel.adding') : t('panel.add'),
|
|
403
474
|
),
|
|
404
475
|
React.createElement(
|
|
405
476
|
'button',
|
|
@@ -423,7 +494,7 @@ window.__ModuleLoader__.load({
|
|
|
423
494
|
opacity: store.busy || !usable ? 0.6 : 1,
|
|
424
495
|
},
|
|
425
496
|
},
|
|
426
|
-
'
|
|
497
|
+
t('panel.refresh'),
|
|
427
498
|
),
|
|
428
499
|
),
|
|
429
500
|
React.createElement(
|
|
@@ -436,7 +507,7 @@ window.__ModuleLoader__.load({
|
|
|
436
507
|
color: 'var(--color-text-muted, #6b6b6b)',
|
|
437
508
|
},
|
|
438
509
|
},
|
|
439
|
-
'
|
|
510
|
+
t('panel.footnote'),
|
|
440
511
|
),
|
|
441
512
|
);
|
|
442
513
|
}
|
|
@@ -477,8 +548,9 @@ window.__ModuleLoader__.load({
|
|
|
477
548
|
|
|
478
549
|
/** Fixed-position hero launcher (B2): visible only while the
|
|
479
550
|
* conversation root reports the hero phase. */
|
|
480
|
-
function HeroLauncher() {
|
|
551
|
+
function HeroLauncher(props) {
|
|
481
552
|
var store = useStore();
|
|
553
|
+
var t = props.t;
|
|
482
554
|
React.useEffect(
|
|
483
555
|
function () {
|
|
484
556
|
var disposers = [];
|
|
@@ -511,7 +583,7 @@ window.__ModuleLoader__.load({
|
|
|
511
583
|
'button',
|
|
512
584
|
{
|
|
513
585
|
type: 'button',
|
|
514
|
-
title: store.heroWorkspace ? '
|
|
586
|
+
title: store.heroWorkspace ? t('title.heroLauncher.hasWorkspace') : t('title.heroLauncher.noWorkspace'),
|
|
515
587
|
onClick: function () { openForWorkspace(store.heroWorkspace); },
|
|
516
588
|
style: {
|
|
517
589
|
position: 'fixed',
|
|
@@ -532,7 +604,7 @@ window.__ModuleLoader__.load({
|
|
|
532
604
|
opacity: store.heroWorkspace ? 1 : 0.7,
|
|
533
605
|
},
|
|
534
606
|
},
|
|
535
|
-
'
|
|
607
|
+
t('label'),
|
|
536
608
|
);
|
|
537
609
|
}
|
|
538
610
|
|
|
@@ -541,12 +613,13 @@ window.__ModuleLoader__.load({
|
|
|
541
613
|
* declares the slot; a no-op registration until then. */
|
|
542
614
|
function HeroChip(props) {
|
|
543
615
|
var store = useStore();
|
|
616
|
+
var t = props.t;
|
|
544
617
|
var workspacePath = props && props.workspacePath ? props.workspacePath : store.heroWorkspace;
|
|
545
618
|
return React.createElement(
|
|
546
619
|
'button',
|
|
547
620
|
{
|
|
548
621
|
type: 'button',
|
|
549
|
-
title: '
|
|
622
|
+
title: t('title.heroChip'),
|
|
550
623
|
onClick: function () { openForWorkspace(workspacePath); },
|
|
551
624
|
style: {
|
|
552
625
|
display: 'inline-flex',
|
|
@@ -561,34 +634,34 @@ window.__ModuleLoader__.load({
|
|
|
561
634
|
cursor: 'pointer',
|
|
562
635
|
},
|
|
563
636
|
},
|
|
564
|
-
'
|
|
637
|
+
t('label'),
|
|
565
638
|
);
|
|
566
639
|
}
|
|
567
640
|
|
|
568
641
|
// Registrations ------------------------------------------------------
|
|
569
642
|
slots.inject('conversation.session.header.actions', function () {
|
|
570
643
|
return slots.register(
|
|
571
|
-
{ name: 'conversation.session.header.actions', id: 'multi-folder', order: 30, label: '
|
|
644
|
+
{ name: 'conversation.session.header.actions', id: 'multi-folder', order: 30, label: function () { return t('label'); }, locale: NS },
|
|
572
645
|
function (props) { return React.createElement(HeaderButton, props); },
|
|
573
646
|
);
|
|
574
647
|
});
|
|
575
648
|
slots.inject('shell.overlay', function () {
|
|
576
649
|
return slots.register(
|
|
577
|
-
{ name: 'shell.overlay', id: 'multi-folder', order: 100, label: '
|
|
578
|
-
function () { return React.createElement(Panel); },
|
|
650
|
+
{ name: 'shell.overlay', id: 'multi-folder', order: 100, label: function () { return t('label'); }, locale: NS },
|
|
651
|
+
function (props) { return React.createElement(Panel, props); },
|
|
579
652
|
);
|
|
580
653
|
});
|
|
581
654
|
slots.inject('shell.overlay', function () {
|
|
582
655
|
return slots.register(
|
|
583
|
-
{ name: 'shell.overlay', id: 'multi-folder-hero', order: 200, label: '
|
|
584
|
-
function () { return React.createElement(HeroLauncher); },
|
|
656
|
+
{ name: 'shell.overlay', id: 'multi-folder-hero', order: 200, label: function () { return t('label.heroLauncher'); }, locale: NS },
|
|
657
|
+
function (props) { return React.createElement(HeroLauncher, props); },
|
|
585
658
|
);
|
|
586
659
|
});
|
|
587
660
|
// Upstream slot (B1): the callback fires only once a DSH build declares
|
|
588
661
|
// `conversation.hero.workspaceExtras`; until then this contributes nothing.
|
|
589
662
|
slots.inject('conversation.hero.workspaceExtras', function () {
|
|
590
663
|
return slots.register(
|
|
591
|
-
{ name: 'conversation.hero.workspaceExtras', id: 'multi-folder', order: 30, label: '
|
|
664
|
+
{ name: 'conversation.hero.workspaceExtras', id: 'multi-folder', order: 30, label: function () { return t('label'); }, locale: NS },
|
|
592
665
|
function (props) { return React.createElement(HeroChip, props); },
|
|
593
666
|
);
|
|
594
667
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-multi-folder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "DeepSeek Harness plugin: secondary working directories for a project. The agent keeps the primary workspace as cwd, gains equal write/exec permissions on configured secondary directories under workspace-write mode, and is notified of configuration changes at the next message boundary. Configurable from the session header AND from the session-creation page (before the first message) through a sessionless multiFolder remote API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh-plugin",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"@deepseek-ai/dsh-api-gateway",
|
|
49
49
|
"@deepseek-ai/dsh-api-remotes",
|
|
50
50
|
"@deepseek-ai/dsh-client-connection",
|
|
51
|
+
"@deepseek-ai/dsh-client-locale",
|
|
51
52
|
"@deepseek-ai/dsh-client-runtime"
|
|
52
53
|
]
|
|
53
54
|
}
|