dsh-jira-tasks 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -4,15 +4,17 @@
4
4
 
5
5
  [中文](README.md) · **English**
6
6
 
7
- Shows the current JIRA project's **open / reopened** issues **assigned to the current user** below the DSH composer input. The JIRA base URL and token are configured in **Settings → Plugins → JIRA** (`JIRA_BASE_URL` / `JIRA_API_TOKEN` act as fallback); the project key and JQL are **configured per workspace** and persisted.
7
+ Shows the current JIRA project's **open / reopened** issues **assigned to the current user** below the DSH composer input. The JIRA base URL and token are configured in **Settings → JIRA 配置** (the settings dialog's left navigation) (`JIRA_BASE_URL` / `JIRA_API_TOKEN` act as fallback); the project key and JQL are **configured per workspace** and persisted.
8
8
 
9
- > **DSH version targeted: 0.1.7 (verified on `0.1.7-rc.2`).** The client slots `conversation.input.dock` / `plugins.item`, the settings service `ctx.configForms` (namespace = profile entry id `jira-tasks`), and the `ctx.effect` / `configForms.whileServed` disposal contracts all follow the 0.1.7 interfaces; the 0.1.6-era `settingsScope` / `settings.register` / `settings.plugin.item` APIs are gone and no longer used.
9
+ > **DSH versions targeted: 0.1.7 and 0.2.0 (verified on `0.1.7-rc.2` and `0.2.0-rc.2`), on both the browser (web profile) and the Desktop app (the Electron-owned desktop profile).**
10
+ > The client slots `conversation.input.dock` / `plugins.item` / `settings.section`, the settings service `ctx.configForms` (namespace = profile entry id `jira-tasks`), and the `ctx.effect` / `configForms.whileServed` disposal contracts are unchanged between 0.1.7 and 0.2.0 (verified package by package), so one `lib/` covers both; the 0.1.6-era `settingsScope` / `settings.register` / `settings.plugin.item` APIs are gone and no longer used.
11
+ > The package declares its DSH range as `>=0.1.7-rc.2 <0.3.0-0` (`engines.dsh` plus an optional `@deepseek-ai/dsh` peer): since 0.2.0 DSH checks that range **on install and on profile startup**, skipping a mismatched bundle and offering the exact-version `allow-version` exemption in the plugin manager. The peer is marked `optional` so `pnpm` never pulls the whole `@deepseek-ai/dsh` tree into the profile.
10
12
 
11
13
  ## Features
12
14
 
13
15
  - 📋 Panel shown below the composer in both new and active sessions (aligned with the input width in new sessions)
14
16
  - 👤 Defaults to the current user (`assignee = currentUser()`) with status `开启 / 重新开启` (Open / Reopened)
15
- - ⚙️ **Settings → Plugins → JIRA** edits the base URL and access token (the token is written to the credential store and never sent back to the browser)
17
+ - ⚙️ **Settings → JIRA 配置** (left navigation, below Agent presets) edits the base URL and access token (the token is written to the credential store and never sent back to the browser); the same JIRA card stays available in the Plugins panel
16
18
  - 🟢 The card auto-probes the connection and shows a status light: green = usable, red = unusable, grey = unconfigured; **Test connection** verifies unsaved drafts immediately
17
19
  - ⚙️ Project key and JQL are saved per workspace; unconfigured workspaces show "unconfigured"
18
20
  - 🔄 Auto-query on every new session, with a one-click refresh (⟳)
@@ -21,7 +23,7 @@ Shows the current JIRA project's **open / reopened** issues **assigned to the cu
21
23
 
22
24
  ## Install
23
25
 
24
- Published to npm:
26
+ Published to npm. For the **browser (web profile)**:
25
27
 
26
28
  ```bash
27
29
  dsh plugin --profile web add dsh-jira-tasks
@@ -35,19 +37,35 @@ dsh plugin --profile web add github:liu3734/jira-tasks-dsh-plugin
35
37
 
36
38
  **Restart DSH** to activate.
37
39
 
38
- > **Nothing shows up? Check `dsh.profile.bundles` first.** DSH mounts this package as a profile layer only when `"dsh-jira-tasks"` is listed in `dsh.profile.bundles` of `~/.dsh/profiles/web/package.json`; being in `dependencies` alone is not enough — the startup log then prints `patch: entry "jira-tasks" not found` and the plugin silently never loads. `dsh plugin --profile web add` normally appends that row, but it will not re-append when the package is already a dependency; add `"dsh-jira-tasks"` to `dsh.profile.bundles` by hand.
40
+ ### Desktop app (DeepSeek Harness.app)
41
+
42
+ The Desktop app does not use the `web` profile; it boots the Electron-owned **`desktop` profile** (`~/.dsh/profiles/desktop/`). That profile name is reserved: an npm-installed `dsh` **refuses** both boot and plugin operations for `--profile desktop` (`profile "desktop" is managed exclusively by the Electron application`). Use the Desktop-provided command instead:
43
+
44
+ 1. Open the Desktop app once (it initializes the desktop profile), then **quit it completely**
45
+ 2. In the app menu, open **Manage dsh Command** → Install (puts the Desktop's own `dsh` on your PATH)
46
+ 3. Install the plugin:
47
+
48
+ ```bash
49
+ dsh plugin --profile desktop add dsh-jira-tasks
50
+ ```
51
+
52
+ 4. Confirm `"dsh-jira-tasks"` is listed in `dsh.profile.bundles` of `~/.dsh/profiles/desktop/package.json`, then start the Desktop app
53
+
54
+ > The renderer's origin is `dsh-app://app`. Electron forwards every non-static request to the local Host with its cookie, so the plugin's absolute-path `fetch("/jira/api/search")` keeps working; `target="_blank"` issue links are handed to the system browser. The project key / JQL live in that origin's localStorage, so **they are separate from the browser configuration at `http://127.0.0.1:<port>`** — configure each surface once.
55
+
56
+ > **Nothing shows up? Check `dsh.profile.bundles` first.** DSH mounts this package as a profile layer only when `"dsh-jira-tasks"` is listed in `dsh.profile.bundles` of that profile's `package.json` (`~/.dsh/profiles/web/package.json` or `~/.dsh/profiles/desktop/package.json`); being in `dependencies` alone is not enough — the startup log then prints `patch: entry "jira-tasks" not found` and the plugin silently never loads. `dsh plugin --profile <name> add` normally appends that row, but it will not re-append when the package is already a dependency; add `"dsh-jira-tasks"` to `dsh.profile.bundles` by hand.
39
57
 
40
58
  > Using GitHub Packages instead: configure `@liu3734:registry=https://npm.pkg.github.com/` plus a read token in the profile's `.npmrc`, then run `dsh plugin --profile web add @liu3734/dsh-jira-tasks`.
41
59
 
42
60
  <details>
43
61
  <summary>Manual install (without npm)</summary>
44
62
 
45
- 1. Copy this repository (its root is the package directory) to `~/.dsh/profiles/web/packages/dsh-jira-tasks/` (skip `.git/`)
46
- 2. Edit `~/.dsh/profiles/web/package.json`:
63
+ 1. Copy this repository (its root is the package directory) into the profile's `packages/dsh-jira-tasks/` (`~/.dsh/profiles/web/` for the browser, `~/.dsh/profiles/desktop/` for the Desktop app; skip `.git/`)
64
+ 2. Edit that profile's `package.json`:
47
65
  - Add to `dependencies`: `"dsh-jira-tasks": "file:./packages/dsh-jira-tasks"`
48
66
  - Append to `dsh.profile.bundles`: `"dsh-jira-tasks"`
49
67
  3. Run `pnpm install` in the profile directory
50
- 4. Restart DSH
68
+ 4. Restart DSH (the Desktop app must be fully quit and reopened)
51
69
 
52
70
  > Note: `pnpm install` **copies** the package into `node_modules/` (not a symlink) — after editing sources, sync `node_modules/dsh-jira-tasks` or re-run install.
53
71
  </details>
@@ -62,7 +80,7 @@ In a DSH session, use the Cordis tools: `cordis_define` (`kind: new`, `idPrefix:
62
80
 
63
81
  ### 1. JIRA base URL and token
64
82
 
65
- Open **Settings → Plugins → Plugin configuration → JIRA** and fill in:
83
+ Open **Settings → JIRA 配置** (the last item in the left navigation, below Agent presets; the JIRA card in the Plugins panel is the same form) and fill in:
66
84
 
67
85
  - **JIRA base URL**: e.g. `http://jira.example.com/` (written to this plugin entry's `Config.baseUrl` — the `jira-tasks` row in the profile's `cordis.patch.yml` — and read back by the form; a host-refused write now surfaces as an error instead of failing silently)
68
86
  - **Access token / PAT**: written to the credential store (`$DSH_HOME/.credentials.yaml`) under the plugin-owned ref `JIRA_TASKS_TOKEN`; the browser only ever sees "configured", never the token itself
@@ -109,10 +127,33 @@ project = "{projectKey}" AND status in ("开启", "重新开启") AND assignee =
109
127
 
110
128
  > The status names follow the Chinese workflow (`开启`/`重新开启`). For English statuses (Open/Reopened), set a custom JQL in ⚙.
111
129
 
130
+ ## Compatibility & verification
131
+
132
+ | Dimension | Coverage |
133
+ |---|---|
134
+ | DSH versions | `0.1.7-rc.2` and `0.2.0-rc.2` (verified); declared range `>=0.1.7-rc.2 <0.3.0-0` — the 0.1.7 and 0.2.x lines, excluding 0.3.0 and its prereleases |
135
+ | Surfaces | Browser (`dsh web` / web profile) and the Desktop app (Electron, desktop profile, renderer origin `dsh-app://app`) |
136
+ | Host APIs | `Config` / `webServer.register` / `subprocess.spawn` / `credentials.resolve` / `settings.configure` — signatures unchanged from 0.1.7 to 0.2.0 |
137
+ | Client APIs | `window.__ModuleLoader__.load`, `slots.inject/register`, `configForms.get/whileServed`, `remote.credentials`, slots `conversation.input.dock` / `plugins.item` / `settings.section` — contracts unchanged from 0.1.7 to 0.2.0 |
138
+
139
+ How it was verified (a throwaway `DSH_HOME`, the user's own profile untouched):
140
+
141
+ ```bash
142
+ # Boot the target dsh in isolation; the profile's dsh.profile.bundles lists dsh-jira-tasks
143
+ DSH_HOME=/tmp/dsh-check node <dsh-0.2.0-rc.2>/lib/bin.js --profile web --no-open --port 19399
144
+
145
+ curl -sX POST http://127.0.0.1:19399/jira/api/test -d '{}' # connectivity / auth
146
+ curl -sX POST http://127.0.0.1:19399/jira/api/search -d '{"projectKey":"<KEY>"}' # issue query
147
+ curl -s "http://127.0.0.1:19399/?token=<token from the startup log>" | grep -o dsh-jira-tasks # client bundle in __DSH_BOOT__
148
+ ```
149
+
150
+ For the Desktop app additionally confirm the client bundle appears in the app's `Code Cache` (the renderer loaded and executed it) and that `dsh.jiraTasks.config.v1` exists in its `localStorage` (the panel works and writes per workspace).
151
+
112
152
  ## Uninstall
113
153
 
114
154
  ```bash
115
- dsh plugin --profile web remove dsh-jira-tasks
155
+ dsh plugin --profile web remove dsh-jira-tasks # browser
156
+ dsh plugin --profile desktop remove dsh-jira-tasks # Desktop app (needs the Desktop-provided dsh)
116
157
  ```
117
158
 
118
159
  ## Troubleshooting
@@ -145,10 +186,11 @@ JIRA_TASKS_TOKEN (settings card) > JIRA_API_TOKEN > JIRA_TOKEN
145
186
  <details>
146
187
  <summary>Panel does not appear</summary>
147
188
 
148
- - Make sure it is installed and DSH was **restarted**; in new sessions the panel sits below the input
149
- - Check that `dsh.profile.bundles` in `~/.dsh/profiles/web/package.json` lists `"dsh-jira-tasks"` (the most common cause of a silent no-show — see the Install note)
150
- - Check the DSH startup log: `patch: entry "jira-tasks" not found` means the profile layer was never composed; `webserver: duplicate exact route` means a route was registered twice (the plugin releases its routes through `ctx.effect`, so this points at a second copy)
151
- - A browser console error `client-modules: could not load "dsh-jira-tasks"` means `/plugins/dsh-jira-tasks/client.js` was not served — confirm `exports["./client"]` resolves to the built `lib/client.js`
189
+ - Make sure it is installed and DSH was **restarted** (the Desktop app must be fully quit and reopened); in new sessions the panel sits below the input
190
+ - Check `dsh.profile.bundles` in the right profile: `~/.dsh/profiles/web/package.json` for the browser, **`~/.dsh/profiles/desktop/package.json` for the Desktop app** (the most common cause of a silent no-show — see the Install note; that profile can only be edited with the Desktop-provided `dsh`)
191
+ - Check the DSH startup log: `patch: entry "jira-tasks" not found` means the profile layer was never composed; `Plugin dsh-jira-tasks@<version> is incompatible with dsh <runtime>` means the running version falls outside the declared `>=0.1.7-rc.2 <0.3.0-0` range, so the bundle is skipped (follow the message, using `dsh plugin allow-version` or the plugin manager, to grant the exact-version exemption); `webserver: duplicate exact route` means a route was registered twice (the plugin releases its routes through `ctx.effect`, so this points at a second copy)
192
+ - A console error `client-modules: could not load "dsh-jira-tasks"` means `/plugins/dsh-jira-tasks/client.js` was not served — confirm `exports["./client"]` resolves to the built `lib/client.js`
193
+ - The Desktop panel says "unconfigured" although you configured it: project key / JQL are stored per **origin**, so `dsh-app://app` (the built-in window) and `http://127.0.0.1:<port>` (a browser) hold two independent configurations
152
194
  </details>
153
195
 
154
196
  ## Architecture & Implementation Details
@@ -163,14 +205,14 @@ JIRA_TASKS_TOKEN (settings card) > JIRA_API_TOKEN > JIRA_TOKEN
163
205
  │ on mount/refresh: fetch POST │ │ credentials.resolve(TOKEN_REFS) │
164
206
  │ render: list / error / unconfigured │ │ subprocess.spawn(curl ...) │
165
207
  │ localStorage: per-workspace key/JQL │ │ parse stdout JSON │
166
- │ plugins.item (Settings card) │ │ return {ok, issues} │
208
+ │ plugins.item (Plugins panel card) │ │ return {ok, issues} │
209
+ │ settings.section (Settings nav page) │ │ │
167
210
  └──────────────────────────────────────────────┘ └────────────────────────────────────┘
168
- └────────────────────────────────┘
169
- ```
170
211
 
171
212
  - **Host**: declares the entry's own `Config` (`baseUrl`, `volatile` — since DSH 0.1.5 a settings page is built from it; `settings.configure({ auto: false }, ctx.fiber)` turns the auto-generated page off and hands its disposer back to `ctx.effect`) plus `webServer` routes `POST /jira/api/search` and `POST /jira/api/test`, each wrapped in its own `ctx.effect` (a reload otherwise hits "duplicate route" and fails activation; non-POST requests get 405); the token comes from the `credentials` service resolved in the order `JIRA_TASKS_TOKEN` (written by the Settings card) → `JIRA_API_TOKEN` → `JIRA_TOKEN` (`$DSH_HOME/.credentials.yaml` / environment, hot-reloaded), so a saved card token overrides the environment; queries run through `subprocess` spawning `curl` directly, with the auth header passed via stdin (`--config -`) so the token never appears in argv.
172
- - **Client**: a standard `window.__ModuleLoader__.load({ id, factory })` web bundle requiring only `"react"`; registers **only `conversation.input.dock`** (`order: 10`) — that seat renders in both new and active sessions and is a full-width row of `composerStack` (`flex-direction: column`), where the panel's own CSS `order: 99` places it below the input card at the card's width. `conversation.composer.dock` is deliberately NOT used: in 0.1.7 it renders into InputBar's `.dock` **row**, side by side with the context meter, so a full-width panel there gets its right-hand content covered by the meter. Also registers the plugin configuration card on `plugins.item` (`id: "jira-tasks"`) — the address form comes from `ctx.configForms.get("jira-tasks")` and the token is written through `remote.credentials` to `JIRA_TASKS_TOKEN`; `configForms.whileServed` keeps it hidden when the host serves no such namespace. The card's `summary` view is rendered by a dispatcher that calls **no hooks**, while the form lives in a separate `JiraSettingsPage`, so flipping `view` on one mounted instance never changes its hook count.
213
+ - **Client**: a standard `window.__ModuleLoader__.load({ id, factory })` web bundle requiring only `"react"`; registers **only `conversation.input.dock`** (`order: 10`) — that seat renders in both new and active sessions and is a full-width row of `composerStack` (`flex-direction: column`), where the panel's own CSS `order: 99` places it below the input card at the card's width. `conversation.composer.dock` is deliberately NOT used: in 0.1.7 and 0.2.0 alike that seat renders into InputBar's `.dock` **row**, side by side with the context meter, so a full-width panel there gets its right-hand content covered by the meter. The address/token form comes from `ctx.configForms.get("jira-tasks")` and the token is written through `remote.credentials` to `JIRA_TASKS_TOKEN`; `configForms.whileServed` keeps it hidden when the host serves no such namespace. It is registered in **two places**: the Plugins-panel card `plugins.item` (`id: "jira-tasks"`, `order: 41`) and a Settings-navigation section `settings.section` (`id: "jira-tasks"`, `order: 30` — above Agent presets' 20, so it sits below it; `label: "JIRA 配置"`; ids outside the shell's `navIcon` allow-list fall back to the default gear icon). Both share one `JiraSettingsPage`, whose outer container is chosen by the external `variant: "page"` prop (`li.jt-set-card` card vs `div.jt-set-page` page) while the field block is the same array. The card's `summary` view is rendered by a dispatcher that calls **no hooks**, while the form lives in a separate `JiraSettingsPage`, so flipping `view` on one mounted instance never changes its hook count.
173
214
  - **Why not the `shell` service**: `shell` wraps commands with `sandbox-exec`, which is broken on some macOS versions (`sandbox_apply: Operation not permitted`); `subprocess` is the raw process seam without this issue.
215
+ - **Why the Desktop app needs no code branch**: its Host is the same DSH as the web profile (same `@deepseek-ai/dsh-web-app` + `dsh-host-webserver`); only the renderer origin differs (`dsh-app://app`), and Electron's `protocol.handle` forwards every non-static request to the local Host with its cookie, so both `fetch("/jira/api/*")` and `/plugins/...` work. `http(s)` `target="_blank"` links are handed to the system browser by the main window's `setWindowOpenHandler`. The only difference that matters is origin-scoped localStorage, so each surface keeps its own project key / JQL.
174
216
  - **One registration covers both states**: `conversation.input.dock` renders whenever a session and its input exist, so new and active sessions need no separate registrations (the older `composer.dock` + blank-session de-duplication was both unnecessary under 0.1.7 and the cause of the meter-row collision).
175
217
 
176
218
  **Differences from the dynamic version**
@@ -180,7 +222,7 @@ JIRA_TASKS_TOKEN (settings card) > JIRA_API_TOKEN > JIRA_TOKEN
180
222
  | Persistence | Lost on restart | Survives restart |
181
223
  | Client→Host | `host.call` / `harness.handle` | `webServer` route + `fetch` |
182
224
  | Client bundle | Injected per session | `/plugins/dsh-jira-tasks/client.js` |
183
- | Config / credentials | Env / `.credentials.yaml` only (no Settings card) | Settings card + same `.credentials.yaml` fallback |
225
+ | Config / credentials | Env / `.credentials.yaml` only (no Settings card) | Settings nav page + Plugins-panel card + same `.credentials.yaml` fallback |
184
226
  </details>
185
227
 
186
228
  ## License
package/README.md CHANGED
@@ -4,9 +4,11 @@
4
4
 
5
5
  **中文** · [English](README.en.md)
6
6
 
7
- 在 DSH 会话**输入框下方**展示当前 JIRA 项目**指派给当前用户**的「开启 / 重新开启」任务列表。JIRA 地址与令牌在**设置 → 插件 → JIRA** 中配置(`JIRA_BASE_URL` / `JIRA_API_TOKEN` 作为回退);项目 Key 与 JQL **按工作区配置**并持久化。
7
+ 在 DSH 会话**输入框下方**展示当前 JIRA 项目**指派给当前用户**的「开启 / 重新开启」任务列表。JIRA 地址与令牌在**设置 → JIRA 配置**(设置对话框左侧导航)中配置(`JIRA_BASE_URL` / `JIRA_API_TOKEN` 作为回退);项目 Key 与 JQL **按工作区配置**并持久化。
8
8
 
9
- > **适配的 DSH 版本:0.1.7(`0.1.7-rc.2` 实测)。** 客户端槽位 `conversation.input.dock` / `plugins.item`、设置服务 `ctx.configForms`(命名空间 = profile 条目 id `jira-tasks`)、`ctx.effect` / `configForms.whileServed` 的注销契约均按 0.1.7 的接口实现;0.1.6 及更早版本的 `settingsScope` / `settings.register` / `settings.plugin.item` 已不再使用。
9
+ > **适配的 DSH 版本:0.1.7 与 0.2.0(`0.1.7-rc.2`、`0.2.0-rc.2` 实测),浏览器(web profile)与桌面端(Electron 独占的 desktop profile)通用。**
10
+ > 客户端槽位 `conversation.input.dock` / `plugins.item` / `settings.section`、设置服务 `ctx.configForms`(命名空间 = profile 条目 id `jira-tasks`)、`ctx.effect` / `configForms.whileServed` 的注销契约在 0.1.7 → 0.2.0 之间没有变化(逐包 diff 过),所以同一份 `lib/` 同时覆盖两版;0.1.6 及更早版本的 `settingsScope` / `settings.register` / `settings.plugin.item` 已不再使用。
11
+ > 包内声明了 DSH 兼容范围 `>=0.1.7-rc.2 <0.3.0-0`(`engines.dsh` + 可选 peer `@deepseek-ai/dsh`):0.2.0 起 DSH 会在**安装与启动**时校验该范围,不满足的版本会被跳过并在插件管理器里给出 `allow-version` 精确豁免入口(peer 标了 `optional`,`pnpm` 不会把整套 `@deepseek-ai/dsh` 装进 profile)。
10
12
 
11
13
  ## 功能
12
14
 
@@ -16,7 +18,7 @@
16
18
  <img width="1972" height="746" alt="image" src="https://github.com/user-attachments/assets/9f543e04-a5ad-4b86-85c8-baa2de26f44e" />
17
19
 
18
20
  - 👤 默认仅显示当前用户(`assignee = currentUser()`)的「开启 / 重新开启」任务
19
- - ⚙️ **设置 → 插件 → JIRA** 配置 JIRA 地址与访问令牌(令牌写入凭据存储,不回传前端)
21
+ - ⚙️ **设置 → JIRA 配置**(设置对话框左侧导航,位于「Agent 预设」下方)配置 JIRA 地址与访问令牌(令牌写入凭据存储,不回传前端);插件面板里的同一张 JIRA 卡片仍然可用
20
22
  - 🟢 设置卡片自动探测连接并显示状态灯:绿=可用、红=不可用、灰=未配置;点「测试连接」可用**未保存的草稿值**即时验证
21
23
 
22
24
  <img width="1590" height="1574" alt="image" src="https://github.com/user-attachments/assets/af5f11c6-aa9a-4c3b-84ca-cff6c0017b22" />
@@ -30,7 +32,7 @@
30
32
 
31
33
  ## 安装
32
34
 
33
- 包已发布到公共 npm(`dsh-jira-tasks`);仓库的发布工作流同时把同一版本发布到 GitHub Packages(作用域包名 `@liu3734/dsh-jira-tasks`)。从 npm 安装(推荐):
35
+ 包已发布到公共 npm(`dsh-jira-tasks`);仓库的发布工作流同时把同一版本发布到 GitHub Packages(作用域包名 `@liu3734/dsh-jira-tasks`)。**浏览器(web profile)**从 npm 安装(推荐):
34
36
 
35
37
  ```bash
36
38
  dsh plugin --profile web add dsh-jira-tasks
@@ -44,19 +46,35 @@ dsh plugin --profile web add github:liu3734/jira-tasks-dsh-plugin
44
46
 
45
47
  **重启 DSH** 后生效。
46
48
 
47
- > **装完没反应?先查 `dsh.profile.bundles`。** DSH 只有当 `~/.dsh/profiles/web/package.json` 的 `dsh.profile.bundles` 里列了 `"dsh-jira-tasks"` 时才把本包当作 profile 层挂载;只出现在 `dependencies` 里不够——此时启动日志会打 `patch: entry "jira-tasks" not found`,插件静默不加载。`dsh plugin --profile web add` 一般会补上这一行,但该包已在 `dependencies` 中时重装不会重新补,手动往 `dsh.profile.bundles` 追加 `"dsh-jira-tasks"` 即可。
49
+ ### 桌面端(DeepSeek Harness.app)
50
+
51
+ 桌面端不用 `web` profile,而是 Electron 独占的 **`desktop` profile**(`~/.dsh/profiles/desktop/`)。该 profile 名被应用保留:**npm 全局安装的 `dsh` 会直接拒绝** `--profile desktop` 的 boot 与 plugin 操作(`profile "desktop" is managed exclusively by the Electron application`),必须用桌面端自带的命令:
52
+
53
+ 1. 先打开一次桌面端(由应用初始化 desktop profile),然后**完全退出桌面端**
54
+ 2. 应用内菜单 → **管理 dsh 命令 / Manage dsh Command** → Install,把桌面端自带的 `dsh` 装进 PATH
55
+ 3. 安装插件:
56
+
57
+ ```bash
58
+ dsh plugin --profile desktop add dsh-jira-tasks
59
+ ```
60
+
61
+ 4. 确认 `~/.dsh/profiles/desktop/package.json` 的 `dsh.profile.bundles` 里有 `"dsh-jira-tasks"`,再启动桌面端
62
+
63
+ > 桌面端渲染进程的 origin 是 `dsh-app://app`。非静态资源的请求由 Electron 转发到本机 Host,所以插件的 `fetch("/jira/api/search")` 这类绝对路径照常可用;任务链接的 `target="_blank"` 由 Electron 交给系统浏览器打开。项目 Key / JQL 存在该 origin 的 localStorage 里,**与浏览器访问 `http://127.0.0.1:<port>` 时的配置互不相通**,两边各配一次即可。
64
+
65
+ > **装完没反应?先查 `dsh.profile.bundles`。** DSH 只有当对应 profile 的 `package.json`(`~/.dsh/profiles/web/package.json` 或 `~/.dsh/profiles/desktop/package.json`)里的 `dsh.profile.bundles` 列了 `"dsh-jira-tasks"` 时才把本包当作 profile 层挂载;只出现在 `dependencies` 里不够——此时启动日志会打 `patch: entry "jira-tasks" not found`,插件静默不加载。`dsh plugin --profile <name> add` 一般会补上这一行,但该包已在 `dependencies` 中时重装不会重新补,手动往 `dsh.profile.bundles` 追加 `"dsh-jira-tasks"` 即可。
48
66
 
49
67
  > 若改用 GitHub Packages 源:先在 profile 的 `.npmrc` 配置 `@liu3734:registry=https://npm.pkg.github.com/` 及读取令牌,再执行 `dsh plugin --profile web add @liu3734/dsh-jira-tasks`。
50
68
 
51
69
  <details>
52
70
  <summary>手动安装(不使用 npm)</summary>
53
71
 
54
- 1. 将本仓库(仓库根目录即包目录)复制为 `~/.dsh/profiles/web/packages/dsh-jira-tasks/`(可忽略 `.git/`)
55
- 2. 编辑 `~/.dsh/profiles/web/package.json`:
72
+ 1. 将本仓库(仓库根目录即包目录)复制为 profile 内的 `packages/dsh-jira-tasks/`(可忽略 `.git/`)——web 用 `~/.dsh/profiles/web/`,桌面端用 `~/.dsh/profiles/desktop/`
73
+ 2. 编辑该 profile 的 `package.json`:
56
74
  - `dependencies` 增加:`"dsh-jira-tasks": "file:./packages/dsh-jira-tasks"`
57
75
  - `dsh.profile.bundles` 追加:`"dsh-jira-tasks"`
58
76
  3. 在 profile 目录执行 `pnpm install`
59
- 4. 重启 DSH
77
+ 4. 重启 DSH(桌面端需完全退出后重新打开)
60
78
 
61
79
  > 注:`pnpm install` 会把包**复制**到 `node_modules/`(非符号链接),改动源码后需同步 `node_modules/dsh-jira-tasks` 或重跑 install。
62
80
  </details>
@@ -71,7 +89,7 @@ dsh plugin --profile web add github:liu3734/jira-tasks-dsh-plugin
71
89
 
72
90
  ### 1. JIRA 地址与令牌
73
91
 
74
- 打开 **设置 → 插件 → 插件配置 → JIRA**,填写:
92
+ 打开 **设置 → JIRA 配置**(左侧导航最后一项,在「Agent 预设」下面;插件面板里的 JIRA 卡片是同一张表单),填写:
75
93
 
76
94
  - **JIRA 地址**:如 `http://jira.example.com/`(写入本插件 profile 条目 `jira-tasks` 的 `Config.baseUrl`,即 profile 的 `cordis.patch.yml`,可在界面回读;保存被宿主拒绝时会直接报错,不会静默失败)
77
95
  - **访问令牌 / PAT**:写入凭据存储(`$DSH_HOME/.credentials.yaml`,引用名是插件自有的 `JIRA_TASKS_TOKEN`),前端只显示"已配置",不回传令牌本身
@@ -118,10 +136,33 @@ project = "{projectKey}" AND status in ("开启", "重新开启") AND assignee =
118
136
 
119
137
  > 状态名按中文工作流("开启"/"重新开启")配置;若 JIRA 用英文状态(Open/Reopened),在 ⚙ 中填写自定义 JQL 即可。
120
138
 
139
+ ## 兼容性与验证
140
+
141
+ | 维度 | 覆盖 |
142
+ |---|---|
143
+ | DSH 版本 | `0.1.7-rc.2`、`0.2.0-rc.2`(实测);声明范围 `>=0.1.7-rc.2 <0.3.0-0`,即 0.1.7 与 0.2.x,不含 0.3.0 及其预发布 |
144
+ | 运行形态 | 浏览器(`dsh web` / web profile)、桌面端 DeepSeek Harness.app(Electron,desktop profile,渲染进程 origin `dsh-app://app`) |
145
+ | 宿主侧 | `Config` / `webServer.register` / `subprocess.spawn` / `credentials.resolve` / `settings.configure`:0.1.7 → 0.2.0 签名未变 |
146
+ | 客户端 | `window.__ModuleLoader__.load`、`slots.inject/register`、`configForms.get/whileServed`、`remote.credentials`、槽位 `conversation.input.dock` / `plugins.item` / `settings.section`:0.1.7 → 0.2.0 契约未变 |
147
+
148
+ 验证方式(不改动用户 profile,临时 `DSH_HOME` 起真实服务):
149
+
150
+ ```bash
151
+ # 用目标版本的 dsh 起一个隔离实例,profile 的 dsh.profile.bundles 含 dsh-jira-tasks
152
+ DSH_HOME=/tmp/dsh-check node <dsh-0.2.0-rc.2>/lib/bin.js --profile web --no-open --port 19399
153
+
154
+ curl -sX POST http://127.0.0.1:19399/jira/api/test -d '{}' # 连通性 / 鉴权
155
+ curl -sX POST http://127.0.0.1:19399/jira/api/search -d '{"projectKey":"<KEY>"}' # 任务查询
156
+ curl -s "http://127.0.0.1:19399/?token=<启动日志里的 token>" | grep -o dsh-jira-tasks # 客户端进入 __DSH_BOOT__ 模块图
157
+ ```
158
+
159
+ 桌面端另需确认:客户端 bundle 出现在应用 `Code Cache`(说明浏览器端已加载执行)、`localStorage` 里存在 `dsh.jiraTasks.config.v1`(说明面板可用且按工作区写入)。
160
+
121
161
  ## 卸载
122
162
 
123
163
  ```bash
124
- dsh plugin --profile web remove dsh-jira-tasks
164
+ dsh plugin --profile web remove dsh-jira-tasks # 浏览器
165
+ dsh plugin --profile desktop remove dsh-jira-tasks # 桌面端(需桌面端自带的 dsh 命令)
125
166
  ```
126
167
 
127
168
  ## 常见问题
@@ -134,8 +175,8 @@ dsh plugin --profile web remove dsh-jira-tasks
134
175
  | 提示 | 处理 |
135
176
  |---|---|
136
177
  | 未设置项目 Key | 面板未配置项目,点标题右侧 ⚙ 填写项目 Key |
137
- | 未配置 JIRA 地址(设置 → 插件 → JIRA,或环境变量 JIRA_BASE_URL) | 地址未写入,见上文「配置 1」 |
138
- | 未配置 JIRA 令牌(设置 → 插件 → JIRA,或环境变量 JIRA_API_TOKEN) | 令牌未写入,见上文「配置 1」 |
178
+ | 未配置 JIRA 地址(设置 → JIRA 配置,或环境变量 JIRA_BASE_URL) | 地址未写入,见上文「配置 1」 |
179
+ | 未配置 JIRA 令牌(设置 → JIRA 配置,或环境变量 JIRA_API_TOKEN) | 令牌未写入,见上文「配置 1」 |
139
180
  | 401 … | 令牌无效或认证方式不对;先 `curl -H "Authorization: Bearer <token>" <base>/rest/api/2/myself` 验证 |
140
181
  | 无法解析 JIRA 响应:… | 网络 / 代理问题,curl 无输出 |
141
182
  </details>
@@ -157,10 +198,11 @@ JIRA_TASKS_TOKEN(设置页) > JIRA_API_TOKEN > JIRA_TOKEN
157
198
  <details>
158
199
  <summary>面板不显示</summary>
159
200
 
160
- - 确认已安装并**重启 DSH**;新会话面板位于输入框下方
161
- - 检查 `~/.dsh/profiles/web/package.json` 的 `dsh.profile.bundles` 是否包含 `"dsh-jira-tasks"`(这是最常见的漏装原因,见「安装」一节的提示)
162
- - 检查 DSH 启动日志:出现 `patch: entry "jira-tasks" not found` 即为 profile 层未挂载;出现 `webserver: duplicate exact route` 说明同一路由被注册了两次(插件自身已用 `ctx.effect` 释放旧路由,若仍有说明有第二份副本)
163
- - 浏览器控制台若报 `client-modules: could not load "dsh-jira-tasks"`,说明 `/plugins/dsh-jira-tasks/client.js` 没取到——确认 `package.json` 的 `exports["./client"]` 指向已构建的 `lib/client.js`
201
+ - 确认已安装并**重启 DSH**(桌面端要完全退出应用再打开);新会话面板位于输入框下方
202
+ - 检查对应 profile 的 `dsh.profile.bundles` 是否包含 `"dsh-jira-tasks"`:浏览器看 `~/.dsh/profiles/web/package.json`,**桌面端看 `~/.dsh/profiles/desktop/package.json`**(最常见的漏装原因,见「安装」一节的提示;桌面端该 profile 只能用应用自带的 dsh 命令改)
203
+ - 检查 DSH 启动日志:出现 `patch: entry "jira-tasks" not found` 即为 profile 层未挂载;出现 `Plugin dsh-jira-tasks@<版本> is incompatible with dsh <运行版本>` 说明运行版本落在声明范围 `>=0.1.7-rc.2 <0.3.0-0` 之外,该包会被跳过(可按提示用 `dsh plugin allow-version` 或插件管理器授精确版本豁免);出现 `webserver: duplicate exact route` 说明同一路由被注册了两次(插件自身已用 `ctx.effect` 释放旧路由,若仍有说明有第二份副本)
204
+ - 浏览器 / 桌面端控制台若报 `client-modules: could not load "dsh-jira-tasks"`,说明 `/plugins/dsh-jira-tasks/client.js` 没取到——确认 `package.json` 的 `exports["./client"]` 指向已构建的 `lib/client.js`
205
+ - 桌面端面板显示「未配置」但你记得配过:项目 Key / JQL 按 **origin** 存 localStorage,`dsh-app://app`(桌面端内置窗口)与 `http://127.0.0.1:<port>`(浏览器)是两份独立配置
164
206
  </details>
165
207
 
166
208
  ## 架构与实现细节
@@ -175,13 +217,15 @@ JIRA_TASKS_TOKEN(设置页) > JIRA_API_TOKEN > JIRA_TOKEN
175
217
  │ ↓ 挂载 / 刷新时 fetch POST │ │ credentials.resolve(TOKEN_REFS) │
176
218
  │ 渲染:任务列表 / 错误 / 未配置 │ │ subprocess.spawn(curl …) │
177
219
  │ localStorage 按工作区存取项目 Key/JQL │ │ ↓ stdout JSON │
178
- │ plugins.item(设置页卡片) │ │ 解析 issues → 返回 {ok,issues} │
220
+ │ plugins.item(插件面板卡片) │ │ 解析 issues → 返回 {ok,issues} │
221
+ │ settings.section(设置导航整页) │ │ │
179
222
  └────────────────────────────────────────┘ └────────────────────────────────────┘
180
223
  ```
181
224
 
182
225
  - **Host**:声明条目自身的 `Config`(`baseUrl`,`volatile`,DSH 0.1.5 起设置页的表单直接来自它;`settings.configure({ auto: false }, ctx.fiber)` 关掉自动生成页,并把返回的注销函数交回 `ctx.effect`)与 `webServer` 路由 `POST /jira/api/search`、`POST /jira/api/test`(两条路由各自包在 `ctx.effect` 里,卸载/重载时先释放,否则重挂载会撞上「重复路由」直接抛错;非 POST 返回 405);令牌经 `credentials` 服务按 `JIRA_TASKS_TOKEN`(设置页写入)→ `JIRA_API_TOKEN` → `JIRA_TOKEN` 的顺序解析(`$DSH_HOME/.credentials.yaml` / 环境变量,热加载),因此设置页保存的令牌能覆盖环境变量;查询用 `subprocess` 直接 `spawn curl`,认证头经 stdin(`--config -`)传入,令牌不进入命令行参数。
183
- - **Client**:`window.__ModuleLoader__.load({ id, factory })` 标准 web bundle,仅 `require("react")`;**只注册 `conversation.input.dock` 一处**(`order: 10`)。该槽位在新会话与活跃会话下都会渲染,属于 `composerStack`(`flex-direction: column`)的整宽纵向行;面板元素自身 `flex order: 99` 排到输入卡之后,即输入框下方,并以 `--dsh-composer-side-clearance` / `--dsh-composer-card-max-width` 与输入卡等宽。之所以不用 `conversation.composer.dock`:0.1.7 里那一槽位渲染进 InputBar 的 `.dock`**横向** flex 行,与上下文占用环并排,整宽面板会被占用环压住右侧内容。插件配置卡片注册到 `plugins.item`(`id: "jira-tasks"`),表单来自 `ctx.configForms.get("jira-tasks")`(地址)与 `remote.credentials`(令牌写入 `JIRA_TASKS_TOKEN`),并用 `configForms.whileServed` 保证宿主未提供该命名空间时不显示;`plugins.item` 的摘要位由一个**不调用任何 hook** 的分发组件负责,表单在独立的 `JiraSettingsPage` 里,避免同一实例在 `view` 切换时改变 hook 数量。
226
+ - **Client**:`window.__ModuleLoader__.load({ id, factory })` 标准 web bundle,仅 `require("react")`;**只注册 `conversation.input.dock` 一处**(`order: 10`)。该槽位在新会话与活跃会话下都会渲染,属于 `composerStack`(`flex-direction: column`)的整宽纵向行;面板元素自身 `flex order: 99` 排到输入卡之后,即输入框下方,并以 `--dsh-composer-side-clearance` / `--dsh-composer-card-max-width` 与输入卡等宽。之所以不用 `conversation.composer.dock`:0.1.7 / 0.2.0 里那一槽位都渲染进 InputBar 的 `.dock`**横向** flex 行,与上下文占用环并排,整宽面板会被占用环压住右侧内容。插件地址与令牌的表单来自 `ctx.configForms.get("jira-tasks")`(地址)与 `remote.credentials`(令牌写入 `JIRA_TASKS_TOKEN`),并用 `configForms.whileServed` 保证宿主未提供该命名空间时不显示。表单**注册在两处**:插件面板卡片 `plugins.item`(`id: "jira-tasks"`,`order: 41`,原入口保留)与设置对话框左侧导航项 `settings.section`(`id: "jira-tasks"`,`order: 30` —— 大于「Agent 预设」的 20,所以显示在它下方;`label: "JIRA 配置"`;未在 `navIcon` 白名单里的 id 由设置外壳回退成默认齿轮图标)。两者共用同一个 `JiraSettingsPage`:由外部 prop `variant: "page"` 决定外层容器(`li.jt-set-card` 卡片 / `div.jt-set-page` 整页),字段区是同一份数组。`plugins.item` 的摘要位由一个**不调用任何 hook** 的分发组件负责,表单在独立的 `JiraSettingsPage` 里,避免同一实例在 `view` 切换时改变 hook 数量。
184
227
  - **为什么不用 `shell` 服务**:`shell` 会套 `sandbox-exec`,部分 macOS 上不可用(`sandbox_apply: Operation not permitted`);`subprocess` 是原始进程缝,无此问题。
228
+ - **桌面端为什么不用改代码**:桌面端 Host 与 web profile 是同一套 DSH(同样是 `@deepseek-ai/dsh-web-app` + `dsh-host-webserver`),渲染进程 origin 是 `dsh-app://app`,非静态资源请求由 Electron 的 `protocol.handle` 带上 Host cookie 转发到本机 Host,所以 `fetch("/jira/api/*")` 与 `/plugins/...` 都照常工作;`http(s)` 的 `target="_blank"` 由主窗口的 `setWindowOpenHandler` 交给系统浏览器。唯一差异是 localStorage 按 origin 隔离,配置需在桌面端与浏览器各存一份。
185
229
  - **一处注册覆盖两种会话**:`conversation.input.dock` 只要有 session + input 就会渲染,新会话与活跃会话无需分别注册(旧版曾用 `composer.dock` + 空白判定去重,0.1.7 下既不必要、又会与占用环抢同一行)。
186
230
 
187
231
  **与动态插件版的差异**
@@ -191,7 +235,7 @@ JIRA_TASKS_TOKEN(设置页) > JIRA_API_TOKEN > JIRA_TOKEN
191
235
  | 持久性 | 重启丢失 | 重启保留 |
192
236
  | Client→Host 通信 | `host.call` / `harness.handle` | `webServer` 路由 + `fetch` |
193
237
  | 客户端 bundle | 会话内注入 | `/plugins/dsh-jira-tasks/client.js` |
194
- | 配置 / 凭据 | 仅环境变量 / `.credentials.yaml`(无设置页卡片) | 设置页卡片 + 同一 `.credentials.yaml` 回退 |
238
+ | 配置 / 凭据 | 仅环境变量 / `.credentials.yaml`(无设置页表单) | 设置导航「JIRA 配置」页 + 插件面板卡片 + 同一 `.credentials.yaml` 回退 |
195
239
  </details>
196
240
 
197
241
  ## License
package/lib/client.js CHANGED
@@ -53,7 +53,12 @@ window.__ModuleLoader__.load({
53
53
  + ".jt-dot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-secondary, #999)}"
54
54
  + ".jt-dot-ok{background:var(--dsw-alias-state-success-primary, #1a9d4f)}"
55
55
  + ".jt-dot-fail{background:var(--dsw-alias-state-error-primary, #d93026)}"
56
- + ".jt-dot-testing{background:var(--dsw-alias-state-warn-primary, #b76e00)}";
56
+ + ".jt-dot-testing{background:var(--dsw-alias-state-warn-primary, #b76e00)}"
57
+ // 设置对话框左侧导航的整页形态(settings.section):
58
+ + ".jt-set-page{box-sizing:border-box;width:100%;max-width:760px;display:flex;flex-direction:column;gap:12px}"
59
+ + ".jt-page-title{margin:0;font-size:18px;font-weight:600;line-height:26px;color:var(--dsw-alias-label-primary, #222)}"
60
+ + ".jt-page-intro{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary, #666)}"
61
+ + ".jt-set-form{display:flex;flex-direction:column}";
57
62
 
58
63
  var STORAGE_KEY = "dsh.jiraTasks.config.v1";
59
64
 
@@ -307,7 +312,9 @@ window.__ModuleLoader__.load({
307
312
  return snapshot;
308
313
  }
309
314
 
310
- function createJiraSettingsCard(scope, credentials) {
315
+ // 返回两处渲染位的组件:{ card } 给 plugins.item,{ section } 给 settings.section。
316
+ // 两者共用同一个 JiraSettingsPage 与同一份 scope / credentials 句柄。
317
+ function createJiraSettingsSurfaces(scope, credentials) {
311
318
  function readTokenInfo() {
312
319
  return credentials.describe(TOKEN_REFS).then(function (res) {
313
320
  if (!res || !res.ok || !res.value) return { configured: false, writable: true };
@@ -337,11 +344,15 @@ window.__ModuleLoader__.load({
337
344
  });
338
345
  }
339
346
 
340
- // plugins.item 的两个渲染位:列表里的卡片摘要(view="summary")与点开后的
341
- // 详情面板(view="page")。分发组件自身不调用任何 hook,钩子全部留在
342
- // JiraSettingsPage 里,这样同一个实例的 hook 数量与 view 取值无关——
343
- // 这是 React 的硬性要求,早期版本把摘要提前 return 写在 hook 之前会踩这条规则。
344
- function JiraSettingsPage() {
347
+ // 同一个表单服务两处渲染位:
348
+ // - plugins.item:列表里的卡片摘要(view="summary")与点开后的详情(其余 view);
349
+ // - settings.section:设置导航里的整页(variant="page")。
350
+ // 分发组件自身不调用任何 hook,钩子全部留在 JiraSettingsPage 里,这样同一个实例
351
+ // 的 hook 数量与 view 取值无关——这是 React 的硬性要求,早期版本把摘要提前 return
352
+ // 写在 hook 之前会踩这条规则。
353
+ function JiraSettingsPage(props) {
354
+ // "page" = 设置对话框左侧导航的整页(settings.section);默认 = 插件面板里的卡片。
355
+ var asPage = !!(props && props.variant === "page");
345
356
  var snapshot = useScopeSnapshot(scope);
346
357
  var tokenInfoState = React.useState(null);
347
358
  var tokenInfo = tokenInfoState[0], setTokenInfo = tokenInfoState[1];
@@ -479,11 +490,9 @@ window.__ModuleLoader__.load({
479
490
  : probe.phase === "fail" ? "jt-dot jt-dot-fail"
480
491
  : probe.phase === "testing" ? "jt-dot jt-dot-testing" : "jt-dot";
481
492
 
482
- return h("li", { className: "jt-set-card" },
483
- h("div", { className: "jt-set-head" },
484
- h("span", { className: "jt-set-title" }, "JIRA"),
485
- h("span", { className: "jt-set-desc" }, "JIRA 地址与访问令牌。项目 Key / JQL 仍按工作区在会话面板的 ⚙ 中配置。")
486
- ),
493
+ // 字段区在两种形态下完全一致,只有外层容器不同:插件面板卡片(li.jt-set-card)
494
+ // vs 设置导航整页(div.jt-set-page)。h.apply 用来展开字段数组,保持 ES5 风格。
495
+ var fields = [
487
496
  h("div", { className: "jt-field" },
488
497
  h("label", { htmlFor: "jira-tasks-base-url" }, "JIRA 地址"),
489
498
  h("input", {
@@ -531,14 +540,36 @@ window.__ModuleLoader__.load({
531
540
  h("button", { className: "jt-btn", disabled: !dirty || busy, onClick: discard }, "放弃"),
532
541
  h("button", { className: "jt-btn jt-btn-primary", disabled: !dirty || busy || (baseDirty && !writable) || (tokenDirty && !tokenWritable), onClick: save }, busy ? "保存中…" : "保存")
533
542
  )
534
- );
543
+ ];
544
+
545
+ if (asPage) {
546
+ return h("div", { className: "jt-set-page" },
547
+ h("h2", { className: "jt-page-title" }, "JIRA 配置"),
548
+ h("p", { className: "jt-page-intro" }, "JIRA 地址与访问令牌(令牌写入凭据存储,不回传前端)。项目 Key / JQL 仍按工作区在会话面板的 ⚙ 中配置。"),
549
+ h.apply(null, ["div", { className: "jt-set-form" }].concat(fields))
550
+ );
551
+ }
552
+
553
+ return h.apply(null, ["li", { className: "jt-set-card" },
554
+ h("div", { className: "jt-set-head" },
555
+ h("span", { className: "jt-set-title" }, "JIRA"),
556
+ h("span", { className: "jt-set-desc" }, "JIRA 地址与访问令牌。项目 Key / JQL 仍按工作区在会话面板的 ⚙ 中配置。")
557
+ )
558
+ ].concat(fields));
535
559
  }
536
560
 
537
- // 槽位组件:摘要位只回一行文字,详情位挂载真正的表单。
538
- return function JiraSettingsCard(props) {
561
+ // 槽位组件:摘要位只回一行文字,详情位挂载真正的表单(不调用任何 hook)。
562
+ function JiraSettingsCard(props) {
539
563
  if (props && props.view === "summary") return "JIRA 地址、令牌与连接状态";
540
564
  return h(JiraSettingsPage, null);
541
- };
565
+ }
566
+
567
+ // 设置对话框左侧导航的一项(settings.section):整页形态的同一个表单。
568
+ function JiraSettingsSection() {
569
+ return h(JiraSettingsPage, { variant: "page" });
570
+ }
571
+
572
+ return { card: JiraSettingsCard, section: JiraSettingsSection };
542
573
  }
543
574
 
544
575
  function apply(ctx) {
@@ -566,20 +597,33 @@ window.__ModuleLoader__.load({
566
597
  return slots.register({ name: "conversation.input.dock", id: "jira-open-tasks", order: 10 }, JiraTasksDock);
567
598
  });
568
599
 
569
- // 设置页卡片。DSH 0.1.5+ :设置表单的服务是 configForms,命名空间就是本插件
570
- // 的 profile 条目 id(jira-tasks);卡片注册到“插件”页的 plugins.item 槽,
571
- // 并且只在宿主确实提供了该命名空间时才出现(whileServed 的 disposer 交回 effect)。
600
+ // 设置表单的两处注册。DSH 0.1.5+ :设置表单的服务是 configForms,命名空间就是
601
+ // 本插件的 profile 条目 id(jira-tasks);两处都只在宿主确实提供了该命名空间
602
+ // 时才出现(whileServed 的 disposer 交回各自的 effect)。
603
+ //
604
+ // 1) 插件面板卡片(plugins.item):插件自己的配置项,原来就在这里。
605
+ // 2) 设置对话框左侧导航项(settings.section):id/order/label 是导航身份,
606
+ // order 大于 Agent 预设(20),所以排在它下面;label 直接给字符串
607
+ // (槽位用 resolveSlotLabel 读,字符串与 thunk 都接受)。未知 id 由设置
608
+ // 外壳回退到默认齿轮图标,无需注册图标。
572
609
  var configForms = ctx.get("configForms");
573
610
  var remote = ctx.get("remote");
574
611
  if (configForms && remote && remote.credentials) {
575
- var card = createJiraSettingsCard(configForms.get(SETTINGS_NS), remote.credentials);
612
+ var surfaces = createJiraSettingsSurfaces(configForms.get(SETTINGS_NS), remote.credentials);
576
613
  ctx.effect(function () {
577
614
  return configForms.whileServed([SETTINGS_NS], function () {
578
615
  return slots.inject("plugins.item", function () {
579
- return slots.register({ name: "plugins.item", id: SETTINGS_NS, order: 41, label: "JIRA" }, card);
616
+ return slots.register({ name: "plugins.item", id: SETTINGS_NS, order: 41, label: "JIRA" }, surfaces.card);
580
617
  });
581
618
  });
582
619
  }, "jira-tasks: plugins page card");
620
+ ctx.effect(function () {
621
+ return configForms.whileServed([SETTINGS_NS], function () {
622
+ return slots.inject("settings.section", function () {
623
+ return slots.register({ name: "settings.section", id: SETTINGS_NS, order: 30, label: "JIRA 配置" }, surfaces.section);
624
+ });
625
+ });
626
+ }, "jira-tasks: settings section");
583
627
  }
584
628
  }
585
629
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-jira-tasks",
3
- "version": "1.1.0",
4
- "description": "JIRA open tasks panel for DeepSeek Harness (DSH) 0.1.7: shows the current user's open/reopened issues below the composer, per-workspace project key, persistent profile bundle.",
3
+ "version": "1.3.0",
4
+ "description": "JIRA open tasks panel for DeepSeek Harness (DSH) 0.1.7 and 0.2.0, web and Desktop: shows the current user's open/reopened issues below the composer, per-workspace project key, persistent profile bundle.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -9,6 +9,17 @@
9
9
  "./client": "./lib/client.js",
10
10
  "./package.json": "./package.json"
11
11
  },
12
+ "engines": {
13
+ "dsh": ">=0.1.7-rc.2 <0.3.0-0"
14
+ },
15
+ "peerDependencies": {
16
+ "@deepseek-ai/dsh": ">=0.1.7-rc.2 <0.3.0-0"
17
+ },
18
+ "peerDependenciesMeta": {
19
+ "@deepseek-ai/dsh": {
20
+ "optional": true
21
+ }
22
+ },
12
23
  "dependencies": {
13
24
  "@deepseek-ai/schemastery": "^3.18.2"
14
25
  },