dsh-super-code 0.3.9 → 0.3.11
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 +36 -11
- package/README.md +36 -11
- package/client/index.js +168 -64
- package/cordis.patch.yml +3 -7
- package/docs/Changelog.md +59 -0
- package/docs/Compatibility.md +51 -3
- package/docs/EvaluationResults.md +21 -5
- package/docs/UsageGuide.md +48 -9
- package/docs/en/Changelog.md +59 -0
- package/docs/en/Compatibility.md +51 -3
- package/docs/en/EvaluationResults.md +21 -5
- package/docs/en/UsageGuide.md +48 -9
- package/lib/connection/directories.d.ts.map +1 -1
- package/lib/connection/directories.js +14 -5
- package/lib/connection/directories.js.map +1 -1
- package/lib/connection/guidance.d.ts.map +1 -1
- package/lib/connection/guidance.js +7 -0
- package/lib/connection/guidance.js.map +1 -1
- package/lib/connection/http.d.ts.map +1 -1
- package/lib/connection/http.js +59 -30
- package/lib/connection/http.js.map +1 -1
- package/lib/connection/service.d.ts +3 -0
- package/lib/connection/service.d.ts.map +1 -1
- package/lib/connection/service.js +82 -33
- package/lib/connection/service.js.map +1 -1
- package/lib/connection/store.d.ts +2 -0
- package/lib/connection/store.d.ts.map +1 -1
- package/lib/connection/store.js +95 -30
- package/lib/connection/store.js.map +1 -1
- package/lib/connection/tasks.d.ts +6 -3
- package/lib/connection/tasks.d.ts.map +1 -1
- package/lib/connection/tasks.js +125 -53
- package/lib/connection/tasks.js.map +1 -1
- package/lib/connection/types.d.ts +1 -0
- package/lib/connection/types.d.ts.map +1 -1
- package/lib/connection/types.js.map +1 -1
- package/lib/core/deletion-command.d.ts.map +1 -1
- package/lib/core/deletion-command.js +84 -23
- package/lib/core/deletion-command.js.map +1 -1
- package/lib/core/windows-runtime.d.ts +16 -0
- package/lib/core/windows-runtime.d.ts.map +1 -0
- package/lib/core/windows-runtime.js +123 -0
- package/lib/core/windows-runtime.js.map +1 -0
- package/lib/dsh/budget-settings.d.ts +2 -0
- package/lib/dsh/budget-settings.d.ts.map +1 -1
- package/lib/dsh/budget-settings.js +9 -3
- package/lib/dsh/budget-settings.js.map +1 -1
- package/lib/dsh/budgets.d.ts +2 -0
- package/lib/dsh/budgets.d.ts.map +1 -1
- package/lib/dsh/budgets.js +6 -0
- package/lib/dsh/budgets.js.map +1 -1
- package/lib/dsh/compaction.d.ts.map +1 -1
- package/lib/dsh/compaction.js +1 -0
- package/lib/dsh/compaction.js.map +1 -1
- package/lib/dsh/connection-history.d.ts.map +1 -1
- package/lib/dsh/connection-history.js +3 -0
- package/lib/dsh/connection-history.js.map +1 -1
- package/lib/dsh/connection-host.js +1 -1
- package/lib/dsh/connection-host.js.map +1 -1
- package/lib/dsh/connection.d.ts.map +1 -1
- package/lib/dsh/connection.js +24 -3
- package/lib/dsh/connection.js.map +1 -1
- package/lib/dsh/deletion-protection.d.ts.map +1 -1
- package/lib/dsh/deletion-protection.js +18 -10
- package/lib/dsh/deletion-protection.js.map +1 -1
- package/lib/dsh/index.d.ts.map +1 -1
- package/lib/dsh/index.js +6 -0
- package/lib/dsh/index.js.map +1 -1
- package/lib/dsh/official-budgets.d.ts.map +1 -1
- package/lib/dsh/official-budgets.js +15 -3
- package/lib/dsh/official-budgets.js.map +1 -1
- package/lib/dsh/official-windows-shell.d.ts +4 -0
- package/lib/dsh/official-windows-shell.d.ts.map +1 -0
- package/lib/dsh/official-windows-shell.js +128 -0
- package/lib/dsh/official-windows-shell.js.map +1 -0
- package/lib/dsh/preset-declaration.d.ts +3 -0
- package/lib/dsh/preset-declaration.d.ts.map +1 -1
- package/lib/dsh/preset-declaration.js +68 -45
- package/lib/dsh/preset-declaration.js.map +1 -1
- package/lib/dsh/preset-installation.d.ts +10 -7
- package/lib/dsh/preset-installation.d.ts.map +1 -1
- package/lib/dsh/preset-installation.js +66 -30
- package/lib/dsh/preset-installation.js.map +1 -1
- package/lib/dsh/preset-metadata.d.ts.map +1 -1
- package/lib/dsh/preset-metadata.js +2 -1
- package/lib/dsh/preset-metadata.js.map +1 -1
- package/lib/dsh/preset-recovery-runtime.d.ts +76 -0
- package/lib/dsh/preset-recovery-runtime.d.ts.map +1 -0
- package/lib/dsh/preset-recovery-runtime.js +322 -0
- package/lib/dsh/preset-recovery-runtime.js.map +1 -0
- package/lib/dsh/preset-recovery.d.ts +14 -0
- package/lib/dsh/preset-recovery.d.ts.map +1 -0
- package/lib/dsh/preset-recovery.js +140 -0
- package/lib/dsh/preset-recovery.js.map +1 -0
- package/lib/dsh/ptc-budget.d.ts.map +1 -1
- package/lib/dsh/ptc-budget.js +20 -10
- package/lib/dsh/ptc-budget.js.map +1 -1
- package/lib/dsh/windows-shell.d.ts +53 -0
- package/lib/dsh/windows-shell.d.ts.map +1 -0
- package/lib/dsh/windows-shell.js +196 -0
- package/lib/dsh/windows-shell.js.map +1 -0
- package/lib/long-memory.d.ts.map +1 -1
- package/lib/long-memory.js +3 -2
- package/lib/long-memory.js.map +1 -1
- package/lib/super-agent-usage.d.ts +2 -0
- package/lib/super-agent-usage.d.ts.map +1 -1
- package/lib/super-agent-usage.js +16 -11
- package/lib/super-agent-usage.js.map +1 -1
- package/lib/task-feedback-projection.d.ts +11 -0
- package/lib/task-feedback-projection.d.ts.map +1 -1
- package/lib/task-feedback-projection.js +37 -12
- package/lib/task-feedback-projection.js.map +1 -1
- package/package.json +21 -16
- package/presets/super-code/agent.cordis.yml +3 -7
- package/runtime/windows/README.md +45 -0
- package/runtime/windows/arm64-busybox.exe.gz +0 -0
- package/runtime/windows/bash.exe.gz +0 -0
- package/runtime/windows/grep.exe.gz +0 -0
- package/runtime/windows/licenses/BusyBox-LICENSE.txt +348 -0
- package/runtime/windows/licenses/GPL-3.0.txt +674 -0
- package/runtime/windows/licenses/LGPL-3.0.txt +165 -0
- package/runtime/windows/licenses/MSYS-COPYING.LIB +165 -0
- package/runtime/windows/licenses/MSYS-COPYING.RUNTIME +674 -0
- package/runtime/windows/licenses/PCRE-LICENCE.txt +93 -0
- package/runtime/windows/licenses/ncurses-COPYING.txt +29 -0
- package/runtime/windows/manifest.json +236 -0
- package/runtime/windows/msys-2.0.dll.gz +0 -0
- package/runtime/windows/msys-iconv-2.dll.gz +0 -0
- package/runtime/windows/msys-intl-8.dll.gz +0 -0
- package/runtime/windows/msys-pcre-1.dll.gz +0 -0
- package/runtime/windows/sources/busybox-w32-FRP-6354-g5f09fb446.tgz +0 -0
- package/runtime/windows/sources.json +45 -0
- package/runtime/windows/x64-busybox.exe.gz +0 -0
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
[中文](../EvaluationResults.md) · English
|
|
4
4
|
|
|
5
|
+
## Pro100: continuous-environment comparison
|
|
6
|
+
|
|
7
|
+
All runs use Harness `0.2.1-rc.2`, `deepseek-v4.1-flash-1017`, high reasoning effort, and legacy budgets. Each group reuses its environment continuously across tasks. These are full 100-task results, sorted by tasks passed.
|
|
8
|
+
|
|
9
|
+
| Configuration | Passed/valid | Mean total tokens (M) | Mean time (min) | Mean model calls | Mean tool calls | Cache rate |
|
|
10
|
+
|---|---:|---:|---:|---:|---:|---:|
|
|
11
|
+
| Super Code 0.3.5 | **51/100** | 6.435 | 24.751 | 76.660 | 75.840 | 98.927% |
|
|
12
|
+
| Official Standard | 50/100 | 8.030 | 17.198 | 91.990 | 113.880 | 99.167% |
|
|
13
|
+
| Unmodified dsh-ops 0.4.4 · Standard | 49/100 | 7.099 | 23.132 | 82.500 | 102.360 | 99.062% |
|
|
14
|
+
| Official Standard + billion-context 0.1.209 | 45/100 | 6.359 | 18.471 | 108.950 | 131.140 | 97.654% |
|
|
15
|
+
| Unmodified dsh-ops 0.4.4 · PTC | 44/100 | 7.322 | 23.695 | 81.490 | 80.960 | 99.056% |
|
|
16
|
+
|
|
17
|
+
M means one million tokens, including reasoning tokens. Time includes some in-run service waits. Super Code `0.3.5` is the continuous legacy-budget baseline for this batch.
|
|
18
|
+
|
|
19
|
+
This batch uses different environment-reuse and time-accounting conventions from the historical budget experiments below. They remain separate; pass counts, times, and efficiency percentages are not mixed. The historical `49/100` and `46/100` results are from earlier runs, rather than the `51/100` and `50/100` in this table. Existing per-task files still describe their original runs.
|
|
20
|
+
|
|
5
21
|
## Evaluation design
|
|
6
22
|
|
|
7
23
|
### Task set
|
|
@@ -10,17 +26,17 @@ SWE-bench Pro hard-100 (frozen ID `swebench-pro-hard-100-ddd22492f1c38839`), gra
|
|
|
10
26
|
|
|
11
27
|
- "hard-100" is this project's selection of 100 tasks by an existing difficulty method, not an official hardest-100 list.
|
|
12
28
|
- Tuning uses only 33 valid hard tasks selected from the SWE-bench dev split. hard-100 is used only to evaluate frozen candidates and investigate issues, **never for tuning**, to avoid overfitting to the evaluation tasks.
|
|
13
|
-
-
|
|
29
|
+
- The historical scores below include later reviews (see [Grading corrections](#grading-corrections)); every run has 100 valid scores.
|
|
14
30
|
|
|
15
31
|
### Baseline
|
|
16
32
|
|
|
17
|
-
|
|
33
|
+
Baselines include the official Standard preset and third-party plugin configurations. The historical official-preset comparison uses Standard rather than Minimal: on the old host, Standard + high passed 51 tasks and Minimal + high passed 46, with Standard using 23.5% fewer tokens per task.
|
|
18
34
|
|
|
19
35
|
### Controlled variables
|
|
20
36
|
|
|
21
|
-
|
|
37
|
+
Comparisons on the same host keep model, reasoning effort, and budgets fixed while comparing preset and plugin combinations. Host or model changes are identified separately. The continuous-environment table above includes some in-run service waits; historical budget experiments use net time, excluding failed requests and retry waits.
|
|
22
38
|
|
|
23
|
-
###
|
|
39
|
+
### Historical process
|
|
24
40
|
|
|
25
41
|
1. **Old-host baseline**: official presets and plugin versions were evaluated on DSH `0.1.5-rc.2` with `deepseek-v4.1-flash`. The Super Code `0.3.0` candidate passed 56 tasks, above the best official result (Standard + high, 51).
|
|
26
42
|
2. **Regression after the host upgrade**: on DSH `0.2.1-rc.2` with `deepseek-v4.1-flash-1017`, under default budgets Standard fell to 46 and Super Code `0.3.4` to 41, below Standard.
|
|
@@ -28,7 +44,7 @@ Within each comparison, host, model, reasoning effort, and budgets are held cons
|
|
|
28
44
|
4. **Retest with legacy budgets**: both sides switched to legacy budgets. Standard stayed at 46; Super Code `0.3.5` rose to 49. Based on this, the plugin uses legacy budgets by default from `0.3.6`.
|
|
29
45
|
5. **Cross-model check**: compared with native ZCode on `glm-5.3-flash` to test whether the approach depends on a specific model.
|
|
30
46
|
|
|
31
|
-
##
|
|
47
|
+
## Historical summary
|
|
32
48
|
|
|
33
49
|
| Comparison | Result |
|
|
34
50
|
|---|---|
|
package/docs/en/UsageGuide.md
CHANGED
|
@@ -11,6 +11,7 @@ New here? Read the Install and Use sections of the [README](../../README.en.md)
|
|
|
11
11
|
- [Sidebar](#sidebar)
|
|
12
12
|
- [Batched tool calls](#batched-tool-calls)
|
|
13
13
|
- [Runtime budgets](#runtime-budgets)
|
|
14
|
+
- [Windows command environment](#windows-command-environment)
|
|
14
15
|
- [Deletion protection](#deletion-protection)
|
|
15
16
|
- [MCP connection](#mcp-connection)
|
|
16
17
|
- [Frontend Skill](#frontend-skill)
|
|
@@ -41,7 +42,7 @@ Before plugin 0.2.0, the settings page lives under **Plugins → Plugin configur
|
|
|
41
42
|
| Tab | Contents |
|
|
42
43
|
|---|---|
|
|
43
44
|
| Home | Preset status and versions, deletion-protection level, MCP switch, connected/past conversation counts |
|
|
44
|
-
| Preset configuration | Install, reinstall, rename |
|
|
45
|
+
| Preset configuration | Install, reinstall, rename, Windows command environment |
|
|
45
46
|
| Context settings | [Runtime budgets](#runtime-budgets) |
|
|
46
47
|
| Deletion protection | [Deletion protection](#deletion-protection) |
|
|
47
48
|
| Connection | [MCP connection](#mcp-connection) |
|
|
@@ -73,13 +74,27 @@ Settings shows the **plugin version** and the **preset version** separately.
|
|
|
73
74
|
|
|
74
75
|
**Safety**:
|
|
75
76
|
|
|
76
|
-
-
|
|
77
|
+
- Directory preset status, language synchronization, and reinstallation verify the install record, UUID, path, and directory identity. Declarative presets verify the source marker or install record. Older copies and same-name declarations with unverified ownership are left unchanged and remain readable.
|
|
77
78
|
- The old content is backed up in `.super-code-backup-*`: under the user preset root for directory hosts, under the current profile for declarative hosts (ownership records in `.super-code-presets.json`).
|
|
78
|
-
-
|
|
79
|
+
- Failed installs roll back their writes and check directory identity before cleanup; substituted directories are preserved. The host then attempts to reload the restored configuration. An originally absent configuration file stays absent. After a crash, or if ownership records remain unwritable, inspect the backup before restoring manually.
|
|
80
|
+
|
|
81
|
+
### Continuing after uninstall
|
|
82
|
+
|
|
83
|
+
After loading a plugin version with recovery support, you can still read existing history when Super Code is uninstalled. Former Super Code conversations continue with the official Standard preset and display a one-time switch notice. The model, working directory and host permissions are preserved. Conversations already using an official preset keep that preset.
|
|
84
|
+
|
|
85
|
+
Turning off deletion protection, disabling its official-preset scope, or disabling or uninstalling the plugin revokes previously injected deletion rules on the next continuation, including Minimal's rules. Switching between strict and relaxed modes retires the previous rule; cold restarts do not add the same rule again. Original history and your own deletion instructions are preserved. If an older rule has already been compacted into a summary, an explicit revocation is added without discarding the summary's other context.
|
|
86
|
+
|
|
87
|
+
Disabling or uninstalling the plugin also releases its command filters, budget adapters, and MCP service. Host permission restrictions and changes owned by other plugins are preserved.
|
|
88
|
+
|
|
89
|
+
If the plugin is reinstalled or the selected preset changes during recovery, the current installation and latest selection take precedence over the fallback. Recovery module updates retain existing conversation registrations and stop late writes from the retired module.
|
|
90
|
+
|
|
91
|
+
If another process changes local recovery or preset ownership records while DSH is running, the plugin stops writing and preserves those files. Check the files, then restart DSH to reload them. An unrelated broken preset does not prevent other usable conversations from being registered.
|
|
92
|
+
|
|
93
|
+
The profile retains `.super-code-recovery` and its configuration entry so recovery works after a cold start without the npm package. It restores registered conversations and revokes old plugin deletion rules; it contains no Super Code tools or model configuration. An upgrade registers older conversations whose preset definitions still exist. If a preset was deleted or renamed before registration, first restore its definition from the installation backup, then reinstall and load the plugin to register the conversation. If Standard is also unavailable, the error is preserved rather than silently choosing another preset.
|
|
79
94
|
|
|
80
95
|
## Sidebar
|
|
81
96
|
|
|
82
|
-
In the right sidebar choose **Super Code**.
|
|
97
|
+
In the right sidebar choose **Super Code**. Exactly one of the three sections stays open, Tasks by default. Clicking the open heading keeps it expanded. Switching keeps reading position, filters, and drafts. The UI follows Harness's Chinese/English setting.
|
|
83
98
|
|
|
84
99
|
### Tasks
|
|
85
100
|
|
|
@@ -87,6 +102,7 @@ Read-only. Shows what's already recorded in the session:
|
|
|
87
102
|
|
|
88
103
|
- **Goal**: the recorded task goal, or the user's original message if there is none.
|
|
89
104
|
- **Steps**: todos the model wrote down, shown as done/total. No steps means no invented plan or progress.
|
|
105
|
+
- **Earlier requests**: prior user requests and their recorded steps stay visible as history, separate from current progress.
|
|
90
106
|
- **Tool calls**: success, failure, and running counts grouped by tool.
|
|
91
107
|
- **Files**: successful file changes that can be confirmed from tool arguments or diffs, including nested PTC calls.
|
|
92
108
|
|
|
@@ -99,11 +115,11 @@ Notes:
|
|
|
99
115
|
|
|
100
116
|
### Agent execution
|
|
101
117
|
|
|
102
|
-
The Agent tree, with status, usage, and cache rate for each Agent.
|
|
118
|
+
The Agent tree, with status, usage, and cache rate for each Agent. Usage counts only calls made by that Agent; forked history is not charged again to the child or the tree total.
|
|
103
119
|
|
|
104
120
|
- Drag empty space to pan; zoom with the wheel or buttons.
|
|
105
121
|
- Click a node to open its details in a new sidebar tab. The main conversation stays put.
|
|
106
|
-
- Details are shown as a chat: user task, Agent replies, expandable tool calls and results. The header shows status, tokens, and cache; open **Details** for more usage, task records, and memory activity.
|
|
122
|
+
- Details are shown as a chat: user task, Agent replies, expandable tool calls and results, including PTC sub-calls and terminal failures. Harness's Markdown and code components render lists, tables, and highlighted code, while tool arguments and output remain literal. The header shows status, tokens, and cache; open **Details** for more usage, task records, and memory activity.
|
|
107
123
|
- Running animations respect the system's reduced-motion setting.
|
|
108
124
|
|
|
109
125
|
The [task](../images/task-status.png) and [tree screenshots](../images/agent-tree.png) render the current plugin with official frontend components and sample data. They illustrate the UI, not model quality.
|
|
@@ -180,9 +196,26 @@ maxContextBytes: 32768 # Super Code: task-memory context bytes
|
|
|
180
196
|
```
|
|
181
197
|
|
|
182
198
|
- A shorter deadline set explicitly on a call still applies.
|
|
199
|
+
- After disabling or uninstalling the plugin, PTC calls whose execution inputs have not yet been resolved use the host deadline. Already-started calls keep their original deadline and are not restarted to restore settings.
|
|
183
200
|
- Older hosts time PTC by busy CPU time; newer hosts by elapsed time. The two aren't comparable.
|
|
184
201
|
- Raising `maxTasks` or `maxContextBytes` uses more context. The defaults are usually fine.
|
|
185
202
|
|
|
203
|
+
## Windows command environment
|
|
204
|
+
|
|
205
|
+
See [Compatibility](Compatibility.md) for the tested scope.
|
|
206
|
+
|
|
207
|
+
On Windows, Super Code defaults to bundled GNU Bash, BusyBox and GNU grep. Common Bash commands work without installing Git for its shell. Developer tools such as Git, Node and Python still need separate installation. macOS and Linux keep the host's existing environment.
|
|
208
|
+
|
|
209
|
+
Turn off **Preset configuration → Command environment → Use bundled Bash (Windows)** to use the host's PowerShell for new conversations. Changes save automatically; running Agents retain their environment, and child Agents inherit their conversation's shell and tool restrictions. Reinstall an older installed preset to pick up the new shell entry. Selection follows the DSH server's platform, independently of the browser's operating system. These switches are editable only on Windows hosts; other platforms show gray, disabled switches with an **Only available on Windows** hover tooltip.
|
|
210
|
+
|
|
211
|
+
Enable **Also apply to official presets** in the same section to use bundled Bash in Standard, PTC and Create; it is off by default. This choice is independent of the budget and deletion-protection scopes. Turning off the main Bash switch also restores the original shell for new official Agents while retaining the saved scope choice. Minimal keeps its persistent terminal; custom presets are not changed automatically. Official presets can use this setting without an installed Super Code preset copy.
|
|
212
|
+
|
|
213
|
+
The plugin carries its runtime and prepares it offline under `%LOCALAPPDATA%\dsh-super-code\shell` on first use. It does not change the system PATH. Settings shows readiness and preparation errors; turn the switch off to use the host environment after an error. Failed commands are never replayed through a different shell. DSH continues to own approvals, deletion protection and process management.
|
|
214
|
+
|
|
215
|
+
The official Windows sandbox blocks the internal named pipes MSYS requires. Bash calls under `read-only` or `workspace-write` therefore fail explicitly before the command runs. If still needed, the Agent can request DSH approval for the **exact same command** with `sandbox_permissions="danger-full-access"` and a justification. Approval permits only that invocation; rejection, cancellation, or disabled approvals leave it unexecuted. The plugin never changes session permissions or switches shells automatically. If the outer PTC `run_code` program also needs wider access, it requires its own host approval; approving Bash does not authorize that outer program.
|
|
216
|
+
|
|
217
|
+
Programs still run on Windows. BusyBox does not implement every GNU option; `.cmd` quoting and native `/` arguments differ from Linux. MSYS converts some paths automatically. Use `MSYS2_ARG_CONV_EXCL` for an individual invocation that needs literal arguments rather than disabling conversion globally. The bundle does not include `trash`, and permanent deletion is never presented as a recycle-bin operation. On ARM64 Windows, Bash, grep and the MSYS DLLs run through x64 emulation. See [Compatibility](Compatibility.md) for tested scope and the [runtime notes](../../runtime/windows/README.md) for sources and licenses.
|
|
218
|
+
|
|
186
219
|
## Deletion protection
|
|
187
220
|
|
|
188
221
|
Under the **Deletion protection** tab. Default is **Relaxed**.
|
|
@@ -227,6 +260,7 @@ Directories:
|
|
|
227
260
|
|
|
228
261
|
- The DSH workspace list is reread every time you open the tab. On first setup, existing workspaces are preselected; workspaces added later are **not** granted access automatically.
|
|
229
262
|
- Add directories with the directory picker. Uncheck to revoke; unchecking the last one also turns MCP off.
|
|
263
|
+
- After revocation, the old directory request reports rejection. The caller must submit a new request to ask for access again.
|
|
230
264
|
|
|
231
265
|
Connections:
|
|
232
266
|
|
|
@@ -234,7 +268,7 @@ Connections:
|
|
|
234
268
|
- Disconnect clients from the Connection tab; idle connections drop after 10 minutes. Disconnecting doesn't cancel accepted tasks.
|
|
235
269
|
- "Connected conversations" on Home means the linked MCP connection is still online, regardless of whether the task finished. After disconnect they count as past conversations.
|
|
236
270
|
|
|
237
|
-
Port conflicts are reported directly. Use the URL shown on the page.
|
|
271
|
+
Port conflicts are reported directly. Choose another port, or resolve the conflict and click **Retry**. If settings were saved but connection cleanup failed, **Retry** restores the connection using those settings. Failed saves retain your draft for retry. Use the URL shown on the page.
|
|
238
272
|
|
|
239
273
|
### Configure the client
|
|
240
274
|
|
|
@@ -250,10 +284,10 @@ Whether a client can set up MCP from the prompt by itself depends on the client.
|
|
|
250
284
|
|---|---|
|
|
251
285
|
| `dsh_status` | Inspect directories, policies, capacity, blocking tasks, and recovery guidance |
|
|
252
286
|
| `dsh_models` | List exact model/effort IDs for `dsh_run` and `dsh_send`; omission retains the current/default selection |
|
|
253
|
-
| `dsh_read` | Page through owned conversations and managed children, including public messages, tool results, and failure details; excludes reasoning and private configuration |
|
|
287
|
+
| `dsh_read` | Page through owned conversations and managed children, including public messages, tool calls/results (also PTC sub-calls), and failure details; excludes reasoning and private configuration |
|
|
254
288
|
| `dsh_approve` | Approve or reject one exact pending operation only when caller approval is enabled |
|
|
255
289
|
| `dsh_sessions` | Call first after connecting: lists recent conversations per directory with status, whether they can be reused, connection capacity, and task concurrency |
|
|
256
|
-
| `dsh_run` | Start a task. Pass `taskId` to reuse a specific conversation, or `newConversation: true` for a new one (passing neither also creates new).
|
|
290
|
+
| `dsh_run` | Start a task. Pass `taskId` to reuse a specific conversation, or `newConversation: true` for a new one (passing neither also creates new). `newConversation: false` requires `taskId` and never creates or automatically picks the latest conversation. Same `requestId` + same input won't dispatch twice; different input is a conflict |
|
|
257
291
|
| `dsh_wait` | Read task status, output, workspace changes, and new events, waiting up to 30 seconds. Without a task ID, lists all tasks page by page |
|
|
258
292
|
| `dsh_send` | Add a follow-up or continue in the same conversation, with a new `requestId`. Never approves permissions for you |
|
|
259
293
|
| `dsh_request_directory` | Ask for a new working directory; you approve it in DSH |
|
|
@@ -263,6 +297,8 @@ Whether a client can set up MCP from the prompt by itself depends on the client.
|
|
|
263
297
|
|
|
264
298
|
The client calls `dsh_request_directory` with a `requestId`, an existing absolute `path`, and a `reason`. DSH shows a global dialog (Settings doesn't need to be open) with the client name, directory, and reason.
|
|
265
299
|
|
|
300
|
+
Before approval, DSH rechecks the requested directory's identity and the request status. Replacing the directory at the same path, expiry, or withdrawal prevents the old request from granting access; use a new `requestId` to request consent again. Ordinary file changes inside that directory do not invalidate consent.
|
|
301
|
+
|
|
266
302
|
- Access is granted only when you click **Allow access**. A model's auto-approval can't do it for you.
|
|
267
303
|
- Repeat the same call to check status: `pending` / `approved` / `rejected` / `expired`.
|
|
268
304
|
- Tasks can't run in that directory until it's approved.
|
|
@@ -278,6 +314,9 @@ The client calls `dsh_request_directory` with a `requestId`, an existing absolut
|
|
|
278
314
|
- `finished`: execution ended, but `acceptance` is always `not-verified`. The caller must check the result.
|
|
279
315
|
- Returned file info is an observation of the Git workspace, including changes that existed before the task, so it doesn't prove the task made them. Non-Git directories or exceeded limits are marked unavailable.
|
|
280
316
|
- Wait timeouts and client disconnects don't cancel tasks. Turning off the connection just stops new requests.
|
|
317
|
+
- Requests still being prepared recheck authorization before creating a conversation, selecting a model, and sending a prompt. Closing the service, revoking its directory, or disconnecting stops unsent prompts; submitted tasks keep running. Reconnecting never automatically resends a saved intent, and an unsent continuation preserves the previous turn's state.
|
|
318
|
+
- Creation, continuation, and cancellation stop if saving their intent fails. A restart during continuation preparation preserves the previous turn's tracking. An unconfirmed new prompt never inherits completion from the previous turn. Task records share a 64 MiB read/write limit; oversized writes are refused without replacing the saved file.
|
|
319
|
+
- Cancelling an MCP wait or approval request prevents a late form answer from granting permission. Service, directory and caller-approval settings are checked again before the decision. A one-time approval already decided is neither undone nor granted again by a later cancellation.
|
|
281
320
|
- After a restart, prompts are never resent automatically. Find the ID via `dsh_sessions` or the task list, check the state, then continue explicitly. If the state can't be confirmed, it shows `interrupted` or `unavailable`; tasks not confirmed as stopped keep their concurrency slot.
|
|
282
321
|
|
|
283
322
|
On capacity refusal, retain the same `requestId` and input and wait through `dsh_status` and `dsh_wait`. Do not cancel other clients' tasks or reconfigure the serving app. Release SDK sessions with `transport.terminateSession()` before `client.close()`; closing alone does not release server capacity. The packaged `super-code-mcp` CLI accepts JSON on stdin and reads its token from the private `DSH_MCP_TOKEN` environment variable.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directories.d.ts","sourceRoot":"","sources":["../../src/connection/directories.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"directories.d.ts","sourceRoot":"","sources":["../../src/connection/directories.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,oBAAoB;IAAG,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACzF,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAC/F,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;CACzD;AACD,0FAA0F;AAC1F,qBAAa,iBAAiB;IAGhB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAkB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAF1E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgE;gBAC3D,OAAO,EAAE,MAAM,MAAM,EAAE,EAAmB,GAAG,GAAE,MAAM,MAAiB;IACnG,OAAO,CAAC,MAAM;IAMR,OAAO,CAAC,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsBrF,OAAO,IAAI,gBAAgB,EAAE;IACvB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAC;IAmBrJ,aAAa,IAAI,IAAI;CACtB"}
|
|
@@ -17,22 +17,25 @@ export class DirectoryRequests {
|
|
|
17
17
|
for (const row of this.requests.values()) {
|
|
18
18
|
if (row.status === 'pending' && this.now() - row.createdAt >= 5 * 60 * 1000)
|
|
19
19
|
row.status = 'expired';
|
|
20
|
+
if (row.status === 'approved' && !this.allowed().some(parent => withinDirectory(parent, row.path)))
|
|
21
|
+
row.status = 'rejected';
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
async request(input, client) {
|
|
23
25
|
this.expire();
|
|
24
26
|
const existing = this.requests.get(input.requestId);
|
|
25
27
|
if (existing) {
|
|
26
|
-
if (this.sources.get(input.requestId) !== input.path || existing.reason !== input.reason)
|
|
28
|
+
if (this.sources.get(input.requestId)?.path !== input.path || existing.reason !== input.reason)
|
|
27
29
|
throw new ConnectionError('request-conflict');
|
|
28
30
|
return { ...existing };
|
|
29
31
|
}
|
|
30
32
|
if (!isAbsolute(input.path))
|
|
31
33
|
throw new ConnectionError('absolute-directory-required');
|
|
32
|
-
let path;
|
|
34
|
+
let path, identity;
|
|
33
35
|
try {
|
|
34
36
|
path = await realpath(input.path);
|
|
35
|
-
|
|
37
|
+
identity = await stat(path, { bigint: true });
|
|
38
|
+
if (!identity.isDirectory())
|
|
36
39
|
throw new Error();
|
|
37
40
|
}
|
|
38
41
|
catch {
|
|
@@ -50,7 +53,7 @@ export class DirectoryRequests {
|
|
|
50
53
|
const row = { id: randomUUID(), ...input, path, client: client.slice(0, 80), createdAt: this.now(),
|
|
51
54
|
status: this.allowed().some(parent => withinDirectory(parent, path)) ? 'approved' : 'pending' };
|
|
52
55
|
this.requests.set(input.requestId, row);
|
|
53
|
-
this.sources.set(input.requestId, input.path);
|
|
56
|
+
this.sources.set(input.requestId, { path: input.path, dev: identity.dev, ino: identity.ino });
|
|
54
57
|
return { ...row };
|
|
55
58
|
}
|
|
56
59
|
pending() { this.expire(); return [...this.requests.values()].filter(row => row.status === 'pending').map(row => ({ ...row })); }
|
|
@@ -63,13 +66,19 @@ export class DirectoryRequests {
|
|
|
63
66
|
return { status: row.status };
|
|
64
67
|
if (decision === 'approved') {
|
|
65
68
|
try {
|
|
66
|
-
if (await realpath(row.path) !== row.path
|
|
69
|
+
if (await realpath(row.path) !== row.path)
|
|
70
|
+
throw new Error();
|
|
71
|
+
const source = this.sources.get(row.requestId), identity = await stat(row.path, { bigint: true });
|
|
72
|
+
if (!source || !identity.isDirectory() || identity.dev !== source.dev || identity.ino !== source.ino)
|
|
67
73
|
throw new Error();
|
|
68
74
|
}
|
|
69
75
|
catch {
|
|
70
76
|
row.status = 'rejected';
|
|
71
77
|
throw new ConnectionError('directory-changed');
|
|
72
78
|
}
|
|
79
|
+
this.expire();
|
|
80
|
+
if (row.status !== 'pending')
|
|
81
|
+
return { status: row.status };
|
|
73
82
|
await grant(row.path);
|
|
74
83
|
}
|
|
75
84
|
row.status = decision;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directories.js","sourceRoot":"","sources":["../../src/connection/directories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"directories.js","sourceRoot":"","sources":["../../src/connection/directories.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guidance.d.ts","sourceRoot":"","sources":["../../src/connection/guidance.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,EAAE,MAAmnD,CAAA;AAExpD,MAAM,WAAW,QAAQ;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE;AACxG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,
|
|
1
|
+
{"version":3,"file":"guidance.d.ts","sourceRoot":"","sources":["../../src/connection/guidance.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,EAAE,MAAmnD,CAAA;AAExpD,MAAM,WAAW,QAAQ;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE;AACxG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CA2BzD"}
|
|
@@ -7,6 +7,9 @@ export function connectionRecovery(code) {
|
|
|
7
7
|
return { action: 'inspect-capacity', message: 'No prompt was admitted. Call dsh_status, wait for blocking tasks with dsh_wait, then retry the same requestId and input. Do not change cwd to evade a shared Git worktree lock or cancel another task.', retryable: true, retryAfterMs: 30000 };
|
|
8
8
|
case 'new-conversation-disabled':
|
|
9
9
|
return { action: 'choose-history', message: 'No new conversation was created. Call dsh_sessions and choose an existing taskId, or ask the user to enable creation.', retryable: false };
|
|
10
|
+
case 'task-id-required':
|
|
11
|
+
case 'conflicting-session-options':
|
|
12
|
+
return { action: 'choose-history', message: 'No prompt was sent. Call dsh_sessions and choose an exact taskId to reuse. newConversation=false requires taskId; newConversation=true cannot be combined with taskId. Do not automatically choose the latest conversation.', retryable: false };
|
|
10
13
|
case 'directory-not-allowed':
|
|
11
14
|
return { action: 'request-directory', message: 'Use dsh_status to inspect grants or dsh_request_directory to request user consent. Do not edit DSH settings or bypass this boundary.', retryable: false };
|
|
12
15
|
case 'caller-approval-disabled':
|
|
@@ -20,6 +23,10 @@ export function connectionRecovery(code) {
|
|
|
20
23
|
return { action: 'recover-task-id', message: 'Call dsh_sessions for the same cwd or dsh_wait without taskId to recover service-owned IDs. Do not recreate or resend an uncertain request.', retryable: false };
|
|
21
24
|
case 'request-conflict':
|
|
22
25
|
return { action: 'check-request', message: 'This requestId already identifies different input. Inspect the prior task; use a new requestId only for a genuinely new operation.', retryable: false };
|
|
26
|
+
case 'request-aborted':
|
|
27
|
+
return { action: 'inspect-task', message: 'This call stopped before dispatching its next action. Inspect the same requestId, taskId or pending approval; saved prompt intents are never automatically resent. Cancellation does not cancel prompts already submitted to DSH or undo approvals already decided.', retryable: false };
|
|
28
|
+
case 'task-storage-full':
|
|
29
|
+
return { action: 'report-storage-limit', message: 'The task history exceeds the 64 MiB storage limit. This write was refused without replacing the saved history. Do not resend an uncertain request or edit the serving profile; report the limit to the user.', retryable: false };
|
|
23
30
|
default:
|
|
24
31
|
return { action: 'report-error', message: 'Report the failure and inspect service/task state through MCP. Do not fall back to controlling the serving DSH application or editing its profile.', retryable: false };
|
|
25
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guidance.js","sourceRoot":"","sources":["../../src/connection/guidance.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,sBAAsB,GAAW,0mDAA0mD,CAAA;AAGxpD,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,gBAAgB,CAAC;QAAC,KAAK,mBAAmB;YAC7C,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,wMAAwM,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,CAAA;QAChS,KAAK,2BAA2B;YAC9B,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,uHAAuH,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QACzL,KAAK,uBAAuB;YAC1B,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,sIAAsI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QAC3M,KAAK,0BAA0B;YAC7B,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,gHAAgH,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QACrL,KAAK,oBAAoB,CAAC;QAAC,KAAK,mBAAmB;YACjD,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,mIAAmI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QACvM,KAAK,sBAAsB;YACzB,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,uLAAuL,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QAC5P,KAAK,gBAAgB;YACnB,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,6IAA6I,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QAChN,KAAK,kBAAkB;YACrB,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,oIAAoI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QACrM;YACE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,oJAAoJ,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;IACtN,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"guidance.js","sourceRoot":"","sources":["../../src/connection/guidance.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,sBAAsB,GAAW,0mDAA0mD,CAAA;AAGxpD,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,gBAAgB,CAAC;QAAC,KAAK,mBAAmB;YAC7C,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,wMAAwM,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,CAAA;QAChS,KAAK,2BAA2B;YAC9B,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,uHAAuH,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QACzL,KAAK,kBAAkB,CAAC;QAAC,KAAK,6BAA6B;YACzD,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,6NAA6N,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QAC/R,KAAK,uBAAuB;YAC1B,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,sIAAsI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QAC3M,KAAK,0BAA0B;YAC7B,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,gHAAgH,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QACrL,KAAK,oBAAoB,CAAC;QAAC,KAAK,mBAAmB;YACjD,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,mIAAmI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QACvM,KAAK,sBAAsB;YACzB,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,uLAAuL,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QAC5P,KAAK,gBAAgB;YACnB,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,6IAA6I,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QAChN,KAAK,kBAAkB;YACrB,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,oIAAoI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QACrM,KAAK,iBAAiB;YACpB,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,qQAAqQ,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QACrU,KAAK,mBAAmB;YACtB,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE,OAAO,EAAE,8MAA8M,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QACtR;YACE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,oJAAoJ,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;IACtN,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/connection/http.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAIhD,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAiB9E,KAAK,gBAAgB,GAAG,CAAC,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAClG,KAAK,UAAU,GAAG,MAAM,MAAM,CAAA;AAC9B,KAAK,gBAAgB,GAAG,MAAM;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAA;AAkBrG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,WAAW,EAAE,YAAY,SAAK,EAAE,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,gBAAgB,CAAC,EAAE,UAAU,GAAE,UAAwB,EAAE,gBAAgB,GAAE,gBAA4H,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/connection/http.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAIhD,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAiB9E,KAAK,gBAAgB,GAAG,CAAC,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAClG,KAAK,UAAU,GAAG,MAAM,MAAM,CAAA;AAC9B,KAAK,gBAAgB,GAAG,MAAM;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAA;AAkBrG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,WAAW,EAAE,YAAY,SAAK,EAAE,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,gBAAgB,CAAC,EAAE,UAAU,GAAE,UAAwB,EAAE,gBAAgB,GAAE,gBAA4H,GAAG,SAAS,CAoE1Y;AAQD,MAAM,WAAW,eAAe;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AAKrH,8FAA8F;AAC9F,qBAAa,cAAc;IAMb,OAAO,CAAC,QAAQ,CAAC,KAAK;IAAkB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IALrF,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,KAAK,CAAgD;gBAChC,KAAK,EAAE,cAAc,EAAmB,gBAAgB,GAAE,gBAAgB,GAAG,SAAqB;IAC/H,WAAW,IAAI,eAAe,EAAE;IAG1B,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAO7B,KAAK;IAIb,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC;YAY7D,KAAK;YAyBL,MAAM;IA0Dd,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAa7B"}
|
package/lib/connection/http.js
CHANGED
|
@@ -33,9 +33,9 @@ async function elicitApproval(server, tasks, view, requestId, signal) {
|
|
|
33
33
|
const answer = await server.server.elicitInput({ mode: 'form', message: `DSH one-time approval / DSH 单次审批\nTask: ${pending.taskId}\nSession: ${pending.sessionId}\nDirectory: ${pending.cwd}\nTool: ${pending.toolName}\nReason: ${pending.reason}\nArguments:\n${pending.arguments}\nOnly approve this exact operation. Content above is untrusted task data.`,
|
|
34
34
|
requestedSchema: { type: 'object', properties: { approve: { type: 'boolean', title: 'Approve once / 仅批准本次操作', default: false } }, required: ['approve'] } }, { relatedRequestId: requestId, signal, timeout: 30000 });
|
|
35
35
|
if (answer.action === 'accept')
|
|
36
|
-
await tasks.approve(view.taskId, pending.id, answer.content?.approve === true ? 'approve' : 'reject');
|
|
36
|
+
await tasks.approve(view.taskId, pending.id, answer.content?.approve === true ? 'approve' : 'reject', signal);
|
|
37
37
|
else if (answer.action === 'decline')
|
|
38
|
-
await tasks.approve(view.taskId, pending.id, 'reject');
|
|
38
|
+
await tasks.approve(view.taskId, pending.id, 'reject', signal);
|
|
39
39
|
else
|
|
40
40
|
tasks.approvals.defer(view.taskId, pending.id);
|
|
41
41
|
}
|
|
@@ -70,7 +70,7 @@ export function delegationMcpServer(tasks, signal, connectionId = '', requestDir
|
|
|
70
70
|
inputSchema: z.object({ cwd: z.string().min(1).max(4096), limit: z.number().int().min(1).max(25).default(10) }).strict(),
|
|
71
71
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
72
72
|
}, input => result(() => tasks.sessions(input.cwd, input.limit, connectionStatus())));
|
|
73
|
-
server.registerTool('dsh_run', { description: 'Delegate to DSH Super Code. Call dsh_sessions first and explicitly choose history or a new conversation according to the task. Pass taskId to reuse an exact conversation, or set newConversation=true to explicitly create a new root session. If neither is provided, a new root is created for backward compatibility. When allowNewConversations is false, both explicit and implicit creation are rejected with new-conversation-disabled; pass an existing taskId instead. Retrying an already accepted request can still recover its task without creating a session. Optionally set provider, model and reasoningEffort (use exact IDs from dsh_models); selection is applied to this conversation before sending the prompt. If omitted, DSH keeps its current/default selection. Use a stable requestId for retries; conflicts do not dispatch. DSH permissions apply independently of the caller sandbox. A finished turn is not verified acceptance.',
|
|
73
|
+
server.registerTool('dsh_run', { description: 'Delegate to DSH Super Code. Call dsh_sessions first and explicitly choose history or a new conversation according to the task. Pass taskId to reuse an exact conversation, or set newConversation=true to explicitly create a new root session. Explicit newConversation=false requires taskId and never creates a conversation. If neither is provided, a new root is created for backward compatibility. When allowNewConversations is false, both explicit and implicit creation are rejected with new-conversation-disabled; pass an existing taskId instead. Retrying an already accepted request can still recover its task without creating a session. Optionally set provider, model and reasoningEffort (use exact IDs from dsh_models); selection is applied to this conversation before sending the prompt. If omitted, DSH keeps its current/default selection. Use a stable requestId for retries; conflicts do not dispatch. DSH permissions apply independently of the caller sandbox. A finished turn is not verified acceptance.',
|
|
74
74
|
inputSchema: z.object({ requestId: identity, cwd: z.string().min(1).max(4096), prompt, client: z.string().min(1).max(80).optional(), taskId: identity.optional(), newConversation: z.boolean().optional(), ...modelFields }).strict().superRefine((value, context) => {
|
|
75
75
|
if ((value.provider === undefined) !== (value.model === undefined))
|
|
76
76
|
context.addIssue({ code: 'custom', message: 'provider and model must be supplied together' });
|
|
@@ -78,24 +78,29 @@ export function delegationMcpServer(tasks, signal, connectionId = '', requestDir
|
|
|
78
78
|
context.addIssue({ code: 'custom', message: 'reasoningEffort requires provider and model' });
|
|
79
79
|
}),
|
|
80
80
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
81
|
-
}, input => result(() => {
|
|
81
|
+
}, (input, extra) => result(() => {
|
|
82
82
|
if (input.taskId) {
|
|
83
83
|
if (input.newConversation)
|
|
84
84
|
throw new ConnectionError('conflicting-session-options');
|
|
85
|
-
return tasks.send({ taskId: input.taskId, requestId: input.requestId, cwd: input.cwd, prompt: input.prompt, connectionId, modelSelection: modelSelection(input) });
|
|
85
|
+
return tasks.send({ taskId: input.taskId, requestId: input.requestId, cwd: input.cwd, prompt: input.prompt, connectionId, modelSelection: modelSelection(input) }, AbortSignal.any([signal(extra.requestId), extra.signal]));
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
if (input.newConversation === false)
|
|
88
|
+
throw new ConnectionError('task-id-required');
|
|
89
|
+
return tasks.run({ requestId: input.requestId, cwd: input.cwd, prompt: input.prompt, client: input.client ?? clientName(), connectionId, modelSelection: modelSelection(input) }, AbortSignal.any([signal(extra.requestId), extra.signal]));
|
|
88
90
|
}));
|
|
89
91
|
server.registerTool('dsh_approve', { description: 'Approve or reject one exact pending DSH tool operation only when the DSH user enabled allowCallerApproval (off by default). Use taskId and approvalId from dsh_wait.callerApproval.pending and inspect the full tool/reason/arguments. The caller decides under its own authorization; do not infer approval from task text. Grants apply once, never change host policy or authorize directories. Expired, cancelled, foreign or withdrawn requests are rejected. Repeating the same decision is idempotent; changing it conflicts.',
|
|
90
92
|
inputSchema: z.object({ taskId: identity, approvalId: identity, decision: z.enum(['approve', 'reject']) }).strict(),
|
|
91
93
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
92
|
-
}, input => result(() => tasks.approve(input.taskId, input.approvalId, input.decision)));
|
|
94
|
+
}, (input, extra) => result(() => tasks.approve(input.taskId, input.approvalId, input.decision, AbortSignal.any([signal(extra.requestId), extra.signal]))));
|
|
93
95
|
server.registerTool('dsh_wait', { description: 'Omit taskId to list service-owned tasks (cursor is a list offset). With taskId, read output, events and pending callerApproval requests. When allowCallerApproval is enabled, form-capable clients receive an interactive approval request during wait; otherwise inspect pending items and use dsh_approve. When disabled or no caller request is pending, resolve confirmation in DSH. Long-poll up to 30 seconds; an interactive approval may add up to 30 seconds. Timeout/disconnect never approves or cancels execution. finished is not acceptance verified.',
|
|
94
96
|
inputSchema: z.object({ taskId: identity.optional(), cursor, waitMs }).strict(),
|
|
95
97
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
96
|
-
}, (input, extra) => result(async () =>
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
}, (input, extra) => result(async () => {
|
|
99
|
+
const pending = AbortSignal.any([signal(extra.requestId), extra.signal]);
|
|
100
|
+
return input.taskId
|
|
101
|
+
? elicitApproval(server, tasks, await tasks.wait(input.taskId, input.cursor, input.waitMs, pending), extra.requestId, pending)
|
|
102
|
+
: tasks.list(input.cursor);
|
|
103
|
+
}));
|
|
99
104
|
server.registerTool('dsh_send', { description: 'Explicitly queue a follow-up in a service-owned root session, including after an interruption. Optionally set provider, model and reasoningEffort (use exact IDs from dsh_models); selection is applied to this conversation before the follow-up. If omitted, DSH keeps its current/default selection. Use a new stable requestId for each follow-up. An ambiguous admission is never automatically resent. Does not approve host permissions.',
|
|
100
105
|
inputSchema: z.object({ taskId: identity, requestId: identity, prompt, ...modelFields }).strict().superRefine((value, context) => {
|
|
101
106
|
if ((value.provider === undefined) !== (value.model === undefined))
|
|
@@ -104,8 +109,8 @@ export function delegationMcpServer(tasks, signal, connectionId = '', requestDir
|
|
|
104
109
|
context.addIssue({ code: 'custom', message: 'reasoningEffort requires provider and model' });
|
|
105
110
|
}),
|
|
106
111
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
107
|
-
}, input => result(() => tasks.send({ taskId: input.taskId, requestId: input.requestId, prompt: input.prompt,
|
|
108
|
-
connectionId, modelSelection: modelSelection(input) })));
|
|
112
|
+
}, (input, extra) => result(() => tasks.send({ taskId: input.taskId, requestId: input.requestId, prompt: input.prompt,
|
|
113
|
+
connectionId, modelSelection: modelSelection(input) }, AbortSignal.any([signal(extra.requestId), extra.signal]))));
|
|
109
114
|
server.registerTool('dsh_cancel', { description: 'Request cancellation of a service-owned root and its managed descendants. Check the returned status; cancelling/unavailable do not confirm that all execution stopped. Already accepted external side effects cannot be undone.',
|
|
110
115
|
inputSchema: z.object({ taskId: identity }).strict(),
|
|
111
116
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
@@ -158,7 +163,7 @@ export class ConnectionHttp {
|
|
|
158
163
|
async start(port, token, maxConnections = 3) {
|
|
159
164
|
if (this.server)
|
|
160
165
|
throw new ConnectionError('already-listening');
|
|
161
|
-
const server = createServer((req, res) => { void this.handle(req, res, port, token, maxConnections).catch(() => { if (!res.headersSent)
|
|
166
|
+
const server = createServer((req, res) => { void this.handle(req, res, server, port, token, maxConnections).catch(() => { if (!res.headersSent)
|
|
162
167
|
reply(res, 500, 'Request failed');
|
|
163
168
|
else
|
|
164
169
|
res.end(); }); });
|
|
@@ -174,10 +179,13 @@ export class ConnectionHttp {
|
|
|
174
179
|
this.timer = setInterval(() => { void this.prune().catch(() => { }); }, 5000);
|
|
175
180
|
this.timer.unref();
|
|
176
181
|
}
|
|
177
|
-
async admit(body, maxConnections) {
|
|
182
|
+
async admit(body, maxConnections, server, signal) {
|
|
178
183
|
if (!body || typeof body !== 'object' || body.method !== 'initialize')
|
|
179
184
|
return false;
|
|
180
185
|
await this.prune();
|
|
186
|
+
// Closing or restarting the listener invalidates admissions already awaiting I/O.
|
|
187
|
+
if (this.server !== server || signal.aborted)
|
|
188
|
+
return false;
|
|
181
189
|
if (this.clients.size >= maxConnections)
|
|
182
190
|
throw new ConnectionError('connection-limit');
|
|
183
191
|
const id = randomUUID(), now = Date.now(), lifetime = new AbortController(), calls = new Map();
|
|
@@ -201,9 +209,17 @@ export class ConnectionHttp {
|
|
|
201
209
|
await this.disconnect(id);
|
|
202
210
|
throw error;
|
|
203
211
|
}
|
|
212
|
+
if (this.server !== server || signal.aborted || lifetime.signal.aborted) {
|
|
213
|
+
await this.disconnect(id);
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
204
216
|
return client;
|
|
205
217
|
}
|
|
206
|
-
async handle(req, res, port, token, maxConnections) {
|
|
218
|
+
async handle(req, res, server, port, token, maxConnections) {
|
|
219
|
+
if (this.server !== server) {
|
|
220
|
+
reply(res, 503, 'MCP listener has closed');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
207
223
|
const address = '127.0.0.1:' + port;
|
|
208
224
|
if (req.headers.host !== address && req.headers.host !== 'localhost:' + port) {
|
|
209
225
|
reply(res, 403, 'Invalid Host');
|
|
@@ -269,6 +285,10 @@ export class ConnectionHttp {
|
|
|
269
285
|
return;
|
|
270
286
|
}
|
|
271
287
|
}
|
|
288
|
+
if (this.server !== server) {
|
|
289
|
+
reply(res, 503, 'MCP listener has closed');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
272
292
|
const session = req.headers['mcp-session-id'];
|
|
273
293
|
if (session !== undefined) {
|
|
274
294
|
client = typeof session === 'string' ? this.clients.get(session) ?? false : false;
|
|
@@ -279,7 +299,7 @@ export class ConnectionHttp {
|
|
|
279
299
|
}
|
|
280
300
|
else {
|
|
281
301
|
try {
|
|
282
|
-
client = await this.admit(body, maxConnections);
|
|
302
|
+
client = await this.admit(body, maxConnections, server, abort.signal);
|
|
283
303
|
admitted = !!client;
|
|
284
304
|
}
|
|
285
305
|
catch (error) {
|
|
@@ -311,16 +331,20 @@ export class ConnectionHttp {
|
|
|
311
331
|
await client.transport.handleRequest(req, res, body);
|
|
312
332
|
}
|
|
313
333
|
finally {
|
|
314
|
-
|
|
315
|
-
client
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
client.calls.
|
|
319
|
-
|
|
320
|
-
|
|
334
|
+
try {
|
|
335
|
+
if (client) {
|
|
336
|
+
client.active--;
|
|
337
|
+
client.lastSeen = Date.now();
|
|
338
|
+
if (requestId !== false && client.calls.get(requestId) === abort)
|
|
339
|
+
client.calls.delete(requestId);
|
|
340
|
+
if (admitted && (!client.transport.sessionId || res.statusCode >= 400 || res.destroyed && !res.writableFinished))
|
|
341
|
+
await this.disconnect(client.id);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
this.pending.delete(abort);
|
|
346
|
+
this.requests--;
|
|
321
347
|
}
|
|
322
|
-
this.pending.delete(abort);
|
|
323
|
-
this.requests--;
|
|
324
348
|
}
|
|
325
349
|
}
|
|
326
350
|
async close() {
|
|
@@ -332,11 +356,16 @@ export class ConnectionHttp {
|
|
|
332
356
|
}
|
|
333
357
|
for (const pending of this.pending)
|
|
334
358
|
pending.abort();
|
|
335
|
-
await Promise.
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
359
|
+
const clients = await Promise.allSettled([...this.clients.keys()].map(id => this.disconnect(id)));
|
|
360
|
+
if (server) {
|
|
361
|
+
server.closeAllConnections();
|
|
362
|
+
await new Promise(resolve => server.close(() => resolve()));
|
|
363
|
+
}
|
|
364
|
+
const failures = clients.filter(result => result.status === 'rejected').map(result => result.reason);
|
|
365
|
+
if (failures.length === 1)
|
|
366
|
+
throw failures[0];
|
|
367
|
+
if (failures.length > 1)
|
|
368
|
+
throw new AggregateError(failures, 'MCP client cleanup failed');
|
|
340
369
|
}
|
|
341
370
|
}
|
|
342
371
|
//# sourceMappingURL=http.js.map
|