dsh-human-wait 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.en.md +69 -0
- package/README.md +39 -154
- package/docs/implementation.en.md +77 -0
- package/docs/implementation.md +77 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.2 — 2026-11-15
|
|
4
|
+
|
|
5
|
+
- Rewrote the README for users: when the plugin helps, what it does and does not change (a table
|
|
6
|
+
with the four cases), how to install and where the setting lives. The Chinese README is now
|
|
7
|
+
primary and `README.en.md` is the English translation.
|
|
8
|
+
- Moved the implementation notes, the acceptance record and the tests to `docs/implementation.md`
|
|
9
|
+
and `docs/implementation.en.md`.
|
|
10
|
+
|
|
3
11
|
## 0.2.1 — 2026-10-27
|
|
4
12
|
|
|
5
13
|
- Adds a browser half (`client.js`), so the plugin gets its own settings page under **Plugins**:
|
package/README.en.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# dsh-human-wait
|
|
2
|
+
|
|
3
|
+
**Stops the PTC `run_code` deadline while DSH is waiting for you.**
|
|
4
|
+
|
|
5
|
+
PTC mode wraps the whole program — including any question, plan review or approval raised mid-run — in one 120-second wall clock. Think a little too long and the run is already gone:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Error: code run failed (timeout): execution deadline reached (120000ms)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
and the question card is reported as **interrupted**. With this plugin, the time you spend answering is no longer charged to that budget; a runaway program is still cut off when the budget runs out.
|
|
12
|
+
|
|
13
|
+
> English | [中文](README.md)
|
|
14
|
+
|
|
15
|
+
## When you need it
|
|
16
|
+
|
|
17
|
+
- You run programs in PTC mode that ask you questions or need approvals, and you want to read before answering
|
|
18
|
+
- You have seen a question card flip to "interrupted", or the timeout above
|
|
19
|
+
|
|
20
|
+
## What it changes, and what it does not
|
|
21
|
+
|
|
22
|
+
| Situation | Without it | With it |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| A question inside the run, answered in 20 s | fine | fine |
|
|
25
|
+
| Same run, answered after 4 minutes | aborted at 120 s, question shown as interrupted | keeps waiting, the answer is delivered |
|
|
26
|
+
| Never answered | aborted at 120 s | aborted once the wait hits the cap (24 h by default) |
|
|
27
|
+
| A program that spins for 10 minutes without asking anything | aborted at 120 s | aborted at 120 s — unchanged |
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
1. Open **Settings → Plugins → Market** and search for `dsh-human-wait`
|
|
32
|
+
2. Install it
|
|
33
|
+
3. **Quit DSH completely, then start it again**
|
|
34
|
+
|
|
35
|
+
## Settings
|
|
36
|
+
|
|
37
|
+
**Settings → Plugins → Installed → Human wait**:
|
|
38
|
+
|
|
39
|
+
- **Longest wait** (24 hours by default; "unlimited" removes the cap)
|
|
40
|
+
- Quick buttons: 1 hour / 24 hours / unlimited
|
|
41
|
+
- **Restore default** appears once the field differs from the shipped value
|
|
42
|
+
- A one-line summary sits on the card
|
|
43
|
+
|
|
44
|
+
A change takes effect in about 1.5 seconds, no restart.
|
|
45
|
+
|
|
46
|
+
## FAQ
|
|
47
|
+
|
|
48
|
+
**It still timed out?** Check three things: the session header shows the `PTC` badge; the plugin's `enabled` is not set to false in the profile patch; the log has a line starting with `[dsh-human-wait]`. If the wrapper did not see that wait, it cannot pause for it.
|
|
49
|
+
|
|
50
|
+
**The error kind became `abort`?** Expected. The plugin aborts through its own signal, so the runtime classifies it as `abort` instead of `timeout`; the text still contains `execution deadline reached`.
|
|
51
|
+
|
|
52
|
+
**Does it affect standard sessions?** No. Standard sessions never had this timeout — they wait indefinitely by design.
|
|
53
|
+
|
|
54
|
+
## Privacy and cost
|
|
55
|
+
|
|
56
|
+
- Wraps three host service calls in memory only; writes no files and opens no network connections
|
|
57
|
+
- Does not modify the conversation, and does not change the program's own execution budget
|
|
58
|
+
|
|
59
|
+
## Uninstall
|
|
60
|
+
|
|
61
|
+
Disable or remove it in the plugin panel, then restart DSH; the host's original methods come back.
|
|
62
|
+
|
|
63
|
+
## Technical notes
|
|
64
|
+
|
|
65
|
+
How it works, the two settings, the fail-open behaviour and the acceptance record: [docs/implementation.en.md](https://github.com/SpaceRelay/dsh-subagent-control/blob/main/human-wait/docs/implementation.en.md)
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT — see [LICENSE](./LICENSE). Issues: [github.com/SpaceRelay/dsh-subagent-control/issues](https://github.com/SpaceRelay/dsh-subagent-control/issues)
|
package/README.md
CHANGED
|
@@ -1,184 +1,69 @@
|
|
|
1
1
|
# dsh-human-wait
|
|
2
2
|
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
wall-clock budget across the whole program, *including the time you spend answering*. Wait
|
|
7
|
-
longer than what is left of that budget and the run dies with:
|
|
3
|
+
**PTC 模式下,DSH 在等你回答问题时,不再让执行超时继续倒数。**
|
|
4
|
+
|
|
5
|
+
PTC(`run_code`)把整个程序——包括中途弹出的问题、计划审批、权限批准——包在一条 120 秒的墙钟预算里。你想久一点再回答,程序就已经超时了:
|
|
8
6
|
|
|
9
7
|
```
|
|
10
8
|
Error: code run failed (timeout): execution deadline reached (120000ms)
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
for execution but pauses it while a human-interaction wait is pending. A runaway program is
|
|
15
|
-
still cut off after its budget; a question you think about for an hour is not.
|
|
16
|
-
|
|
17
|
-
> 中文摘要:PTC 模式下提问、计划审批和批准都是 `run_code` 里的嵌套调用,共用同一条 120 秒墙钟预算,
|
|
18
|
-
> 所以「想久一点再回答」会让你看到的是一次 `execution deadline reached (120000ms)` 超时。本插件
|
|
19
|
-
> 保留执行的截止时间,但在**等待你回答/批准期间暂停计时**;等待本身另有上限(默认 24 小时,可设为无限)。
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## What it does
|
|
24
|
-
|
|
25
|
-
| Situation | Without the plugin | With the plugin |
|
|
26
|
-
| --- | --- | --- |
|
|
27
|
-
| A question inside `run_code`, you answer in 20 s, run budget was 120 s | OK | OK |
|
|
28
|
-
| Same run, you answer after 4 minutes | run aborted at 120 s, question interrupted | run keeps waiting; the answer is delivered |
|
|
29
|
-
| Same run, you never answer | run aborted at 120 s | run aborted once the wait exceeds `humanWaitCapMs` (24 h) |
|
|
30
|
-
| A program that spins for 10 minutes without asking anything | aborted at 120 s | aborted at 120 s — unchanged |
|
|
31
|
-
|
|
32
|
-
It works by:
|
|
33
|
-
|
|
34
|
-
1. wrapping `ptcRuntime.run` and moving the deadline into the plugin, so it can be paused;
|
|
35
|
-
2. wrapping `userQuestions.ask` / `userQuestions.askTimed` and `approval.request` — the three
|
|
36
|
-
service calls that exist only because a human is being waited on;
|
|
37
|
-
3. charging the budget only while no such wait is pending (`node:async_hooks`
|
|
38
|
-
`AsyncLocalStorage` keeps the accounting scoped to the run that is actually waiting).
|
|
11
|
+
问题卡还会被标成「已中断」。装上这个插件,等待你回答的那段时间不再计入预算;程序自己跑飞了,照样在预算到点时被掐断。
|
|
39
12
|
|
|
40
|
-
|
|
41
|
-
execution budget and is what the plugin enforces.
|
|
13
|
+
> [English](README.en.md) | 中文
|
|
42
14
|
|
|
43
|
-
##
|
|
15
|
+
## 什么时候用得上
|
|
44
16
|
|
|
45
|
-
|
|
46
|
-
|
|
17
|
+
- 用 PTC 模式跑程序,中途要你回答问题或审批,而你想读完再答
|
|
18
|
+
- 遇到过「问题卡突然变成已中断」或者上面那条超时
|
|
47
19
|
|
|
48
|
-
|
|
49
|
-
`plugin_manager install_bundle` call in a session, with the spec `dsh-human-wait@0.2.1`; the
|
|
50
|
-
plugin manager adds both the dependency and the bundle entry for you.
|
|
20
|
+
## 它改什么、不改什么
|
|
51
21
|
|
|
52
|
-
|
|
53
|
-
|
|
22
|
+
| 情况 | 没装 | 装了 |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| 程序里提问,20 秒答完 | 正常 | 正常 |
|
|
25
|
+
| 同一个程序,4 分钟后才答 | 120 秒就被中止,问题卡显示已中断 | 继续等,答案照常送达 |
|
|
26
|
+
| 一直不答 | 120 秒中止 | 等到最长等待上限(默认 24 小时)才中止 |
|
|
27
|
+
| 程序自己死循环 10 分钟 | 120 秒中止 | 120 秒中止,行为不变 |
|
|
54
28
|
|
|
55
|
-
|
|
56
|
-
{
|
|
57
|
-
"dependencies": {
|
|
58
|
-
"dsh-human-wait": "file:D:/path/to/dsh-human-wait-0.2.1.tgz"
|
|
59
|
-
},
|
|
60
|
-
"dsh": { "profile": { "bundles": ["dsh-human-wait"] } }
|
|
61
|
-
}
|
|
62
|
-
```
|
|
29
|
+
## 安装
|
|
63
30
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```
|
|
31
|
+
1. 打开 设置 → 插件 → 市场,搜索 `dsh-human-wait`
|
|
32
|
+
2. 点安装
|
|
33
|
+
3. **完全退出 DSH,再重新打开**
|
|
68
34
|
|
|
69
|
-
|
|
70
|
-
Electron-managed profile cannot be booted through the `dsh` CLI (`profile "desktop" is managed
|
|
71
|
-
exclusively by the Electron application`), so restarting the app is the only way to apply it.
|
|
72
|
-
Removing the bundle name from `dsh.profile.bundles` and deleting the dependency disables the
|
|
73
|
-
plugin again; the wrappers are also restored at unload time.
|
|
35
|
+
## 设置
|
|
74
36
|
|
|
75
|
-
|
|
37
|
+
设置 → 插件 → 已安装 → **人机等待**:
|
|
76
38
|
|
|
77
|
-
|
|
78
|
-
|
|
39
|
+
- **最长等待**(默认 24 小时;选「无限」表示不设上限)
|
|
40
|
+
- 快捷按钮:1 小时 / 24 小时 / 无限
|
|
41
|
+
- 字段偏离出厂值时会出现「恢复默认」
|
|
42
|
+
- 卡片上有一行摘要
|
|
79
43
|
|
|
80
|
-
|
|
81
|
-
- id: human-wait
|
|
82
|
-
name: 'dsh-human-wait'
|
|
83
|
-
config:
|
|
84
|
-
enabled: true
|
|
85
|
-
humanWaitCapMs: 86400000 # 24 h; -1 = no cap at all
|
|
86
|
-
```
|
|
44
|
+
改完约 1.5 秒生效,不用重启。
|
|
87
45
|
|
|
88
|
-
|
|
89
|
-
| --- | --- | --- |
|
|
90
|
-
| `enabled` | `true` | Turn the pause on/off. `false` reproduces the shipped behaviour exactly. |
|
|
91
|
-
| `humanWaitCapMs` | `86400000` | Longest time one run may spend waiting for a human. `-1` removes the cap. |
|
|
92
|
-
|
|
93
|
-
How the clock works: the plugin re-arms a single timer on every accounting event (run start, a
|
|
94
|
-
wait starting or ending, or the timer itself firing), so there is no fixed sampling period and
|
|
95
|
-
no configuration knob for one.
|
|
96
|
-
|
|
97
|
-
Both options are **volatile**: they are read again while the plugin is running, so a change
|
|
98
|
-
takes effect within about a second and a half, with no restart. Editing the
|
|
99
|
-
patch by hand is still read at boot.
|
|
100
|
-
|
|
101
|
-
## Settings page
|
|
102
|
-
|
|
103
|
-
The plugin ships a browser half, so it gets its own card on its page under **Plugins** (the same
|
|
104
|
-
place as Team limits):
|
|
105
|
-
|
|
106
|
-
- a number field for `humanWaitCapMs`, with quick buttons for 1 hour / 24 hours / unlimited;
|
|
107
|
-
- an "overridden" chip and a "restore default" action for that field;
|
|
108
|
-
- a one-line summary on the card itself.
|
|
109
|
-
|
|
110
|
-
`enabled` stays in the patch layer (`cordis.patch.yml`), where it is read live within about a
|
|
111
|
-
second and a half.
|
|
112
|
-
|
|
113
|
-
The values are written back to this profile's patch layer through the normal settings path;
|
|
114
|
-
uninstalling the plugin does not roll them back.
|
|
115
|
-
|
|
116
|
-
## What counts as a human wait
|
|
117
|
-
|
|
118
|
-
- `userQuestions.ask` — blocking questions, including the `plan-review` question from
|
|
119
|
-
`exit_plan_mode`;
|
|
120
|
-
- `userQuestions.askTimed` — timed questions;
|
|
121
|
-
- `approval.request` — sandbox/policy approvals that block on the user.
|
|
122
|
-
|
|
123
|
-
Tool calls that finish on their own (bash, file reads, web fetches, subagents) are **not**
|
|
124
|
-
waits: their time is still charged to the run, exactly as before.
|
|
125
|
-
|
|
126
|
-
## Behaviour notes
|
|
127
|
-
|
|
128
|
-
- **Fail-open.** If `ptcRuntime` / `userQuestions` / `approval` are missing, if
|
|
129
|
-
`AbortSignal.any` is unavailable, or if a wrapper cannot be installed, the plugin logs one
|
|
130
|
-
warning and leaves the shipped deadline untouched.
|
|
131
|
-
- **Restored on unload.** Every wrapper is removed when the plugin unloads (or is disabled and
|
|
132
|
-
the profile restarted); the original methods are back.
|
|
133
|
-
- **The terminal error changes kind.** When the paused execution budget runs out, the runtime
|
|
134
|
-
reports `kind: "abort"` instead of `kind: "timeout"`, because the plugin aborts the run
|
|
135
|
-
through its signal. The message still contains `execution deadline reached (<budget>ms)`, so
|
|
136
|
-
the surfaced text is the same.
|
|
137
|
-
- **Only PTC sessions are affected.** Standard sessions never had this timeout: their
|
|
138
|
-
questions wait indefinitely by design.
|
|
139
|
-
- **Not a public extension point.** The plugin patches methods of two host services
|
|
140
|
-
(`ptcRuntime`, `userQuestions`) and one approval service. That is the only way to implement
|
|
141
|
-
this today; a future DSH release may change those internals and make the patch a no-op. The
|
|
142
|
-
plugin is written so that this failure mode is safe, not fatal.
|
|
143
|
-
|
|
144
|
-
Verified against DSH desktop **0.2.1-rc.3** (`dsh-ptc-runtime-node` 0.2.1, node 24.21).
|
|
145
|
-
`ptc-runtime-node/lib/index.js` is where the shipped timer lives:
|
|
146
|
-
|
|
147
|
-
```js
|
|
148
|
-
const wallTimer = spec.timeoutMs === null ? void 0 : setTimeout(() => {
|
|
149
|
-
timedOut = true;
|
|
150
|
-
controller.abort("execution deadline reached");
|
|
151
|
-
}, spec.timeoutMs);
|
|
152
|
-
```
|
|
46
|
+
## 常见问题
|
|
153
47
|
|
|
154
|
-
|
|
48
|
+
**还是超时了?** 确认三件事:会话头部显示 PTC 标记;插件的 `enabled` 没在 profile 补丁里被关掉;日志里有 `[dsh-human-wait]` 开头的那行说明。如果等待期间恰好插件没看到这次等待,它就不会暂停计时。
|
|
155
49
|
|
|
156
|
-
|
|
157
|
-
`PTC run deadlines now pause while a human is waiting (wait cap: …)` or the reason it stayed
|
|
158
|
-
out of the way.
|
|
159
|
-
- If a wait still dies with `execution deadline reached`, the wrapper did not see the wait.
|
|
160
|
-
Check that PTC mode is actually in use (the session header shows the `PTC` badge) and that
|
|
161
|
-
`enabled` is not `false`.
|
|
50
|
+
**错误类型变成 `abort` 了?** 正常。插件用自己的信号中止程序,所以 kind 从 `timeout` 变成 `abort`,提示文字里仍然有 `execution deadline reached`。
|
|
162
51
|
|
|
163
|
-
|
|
52
|
+
**普通会话受影响吗?** 不受影响。普通会话本来就会一直等你回答,没有这个超时。
|
|
164
53
|
|
|
165
|
-
|
|
166
|
-
npm test # node --test, no dependencies
|
|
167
|
-
```
|
|
54
|
+
## 隐私与开销
|
|
168
55
|
|
|
169
|
-
|
|
56
|
+
- 只在内存里包住三个宿主服务的调用,不写文件、不联网
|
|
57
|
+
- 不修改会话内容,也不改变程序本身的执行预算
|
|
170
58
|
|
|
171
|
-
|
|
59
|
+
## 卸载
|
|
172
60
|
|
|
173
|
-
|
|
61
|
+
在插件面板里停用或卸载,然后重启 DSH;宿主原来的方法会恢复。
|
|
174
62
|
|
|
175
|
-
|
|
63
|
+
## 技术细节
|
|
176
64
|
|
|
177
|
-
|
|
178
|
-
| --- | --- | --- |
|
|
179
|
-
| A | nested `ask_user_question`, answered after 294037 ms | answer delivered, run continued, no `execution deadline reached` |
|
|
180
|
-
| B | plan review submitted through `exit_plan_mode`, approved after 313073 ms | plan approved, run continued, no automatic cancellation at 120 s |
|
|
181
|
-
| C | `run_code` running `while (true) {}` | killed after ~120 s with `Error: code run failed (abort): execution deadline reached (120000ms)` |
|
|
65
|
+
实现原理、两个配置项、失败时的降级行为与验收记录:[docs/implementation.md](https://github.com/SpaceRelay/dsh-subagent-control/blob/main/human-wait/docs/implementation.md)
|
|
182
66
|
|
|
183
|
-
|
|
67
|
+
## 许可
|
|
184
68
|
|
|
69
|
+
MIT,见 [LICENSE](./LICENSE)。问题反馈:[github.com/SpaceRelay/dsh-subagent-control/issues](https://github.com/SpaceRelay/dsh-subagent-control/issues)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# dsh-human-wait — implementation notes
|
|
2
|
+
|
|
3
|
+
For maintainers. User-facing docs live in [../README.en.md](../README.en.md).
|
|
4
|
+
|
|
5
|
+
## The problem
|
|
6
|
+
|
|
7
|
+
In PTC mode, `dsh-ptc-runtime-node` puts one wall-clock budget around the entire `run_code` program, nested tool calls and approval waits included:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
const wallTimer = spec.timeoutMs === null ? void 0 : setTimeout(() => {
|
|
11
|
+
timedOut = true
|
|
12
|
+
controller.abort("execution deadline reached")
|
|
13
|
+
}, spec.timeoutMs)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
So the time spent waiting for you burns the budget too. This project takes the timer over and charges it only while no human-interaction wait is pending.
|
|
17
|
+
|
|
18
|
+
## How it works
|
|
19
|
+
|
|
20
|
+
1. Wrap `ptcRuntime.run`: the deadline moves into the plugin, which can pause it.
|
|
21
|
+
2. Wrap the three points where a human is actually being waited on: `userQuestions.ask`, `userQuestions.askTimed`, `approval.request`.
|
|
22
|
+
3. Use `node:async_hooks` `AsyncLocalStorage` to scope the accounting to the run that is really waiting, charging the budget only while no such wait is pending.
|
|
23
|
+
|
|
24
|
+
A single `run_code` call can still pass its own `timeoutMs`; that number keeps working as the execution budget.
|
|
25
|
+
|
|
26
|
+
## Configuration
|
|
27
|
+
|
|
28
|
+
The row ships sensible defaults; override them in the same profile's `cordis.patch.yml`:
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
- id: human-wait
|
|
32
|
+
name: 'dsh-human-wait'
|
|
33
|
+
config:
|
|
34
|
+
enabled: true
|
|
35
|
+
humanWaitCapMs: 86400000 # 24 h; -1 = no cap at all
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| Option | Default | Meaning |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `enabled` | `true` | Master switch for pausing. `false` reproduces the shipped behaviour exactly. |
|
|
41
|
+
| `humanWaitCapMs` | `86400000` | Longest time one run may spend waiting for a human. `-1` removes the cap. |
|
|
42
|
+
|
|
43
|
+
Both options are **volatile**: they are re-read while the plugin runs, so a change applies in about 1.5 seconds without a restart. A hand-edited patch is read at boot.
|
|
44
|
+
|
|
45
|
+
## Settings page
|
|
46
|
+
|
|
47
|
+
`client.js` is a hand-written browser half registered on the `plugins.bundle.config` slot (key `dsh-human-wait`): a `humanWaitCapMs` number field, 1 hour / 24 hours / unlimited quick buttons, a field-level "overridden" chip with "restore default", and a one-line summary on the card. `enabled` stays in the patch layer.
|
|
48
|
+
|
|
49
|
+
## Behaviour notes
|
|
50
|
+
|
|
51
|
+
- **Fail-open.** If `ptcRuntime` / `userQuestions` / `approval` are missing, if `AbortSignal.any` is unavailable, or if a wrapper cannot be installed, the plugin logs one warning and leaves the shipped deadline untouched.
|
|
52
|
+
- **Restored on unload.** Every wrapper is removed when the plugin unloads (or is disabled and the profile restarted); the original methods are back.
|
|
53
|
+
- **The terminal error changes kind.** When the paused execution budget runs out, the runtime reports `kind: "abort"` instead of `kind: "timeout"`, because the plugin aborts the run through its signal. The message still contains `execution deadline reached (<budget>ms)`.
|
|
54
|
+
- **Only PTC sessions are affected.** Standard sessions never had this timeout: their questions wait indefinitely by design.
|
|
55
|
+
- **Not a public extension point.** The plugin patches methods of two host services (`ptcRuntime`, `userQuestions`) and one approval service. That is the only way to implement this today; a future DSH release may change those internals and make the patch a no-op. The plugin is written so that this failure mode is safe, not fatal.
|
|
56
|
+
|
|
57
|
+
## Verified environment and acceptance record
|
|
58
|
+
|
|
59
|
+
Verified against DSH desktop **0.2.1-rc.3** (`dsh-ptc-runtime-node` 0.2.1, Node 24.21).
|
|
60
|
+
|
|
61
|
+
Recorded 2026-10-26 against `dsh-human-wait` 0.1.0 in the desktop profile (PTC mode, shipped 120 s budget):
|
|
62
|
+
|
|
63
|
+
| Test | Scenario | Observed |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| A | nested `ask_user_question`, answered after 294037 ms | answer delivered, run continued, no `execution deadline reached` |
|
|
66
|
+
| B | plan review submitted through `exit_plan_mode`, approved after 313073 ms | plan approved, run continued, no automatic cancellation at 120 s |
|
|
67
|
+
| C | `run_code` running `while (true) {}` | killed after ~120 s with `Error: code run failed (abort): execution deadline reached (120000ms)` |
|
|
68
|
+
|
|
69
|
+
Test C's `(abort)` label is expected — the plugin aborts through its own signal, so the runtime classifies the failure as `abort` instead of `timeout` while the message text stays `execution deadline reached (120000ms)`.
|
|
70
|
+
|
|
71
|
+
## Tests
|
|
72
|
+
|
|
73
|
+
```powershell
|
|
74
|
+
npm test # node --test, no dependencies
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Coverage: re-reading both settings, wrapping and restoring the three wait points, the fail-open branches, and the browser half (bundle id, slot key, page rendering).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# dsh-human-wait 实现说明
|
|
2
|
+
|
|
3
|
+
面向维护者。用户向说明见 [../README.md](../README.md)。
|
|
4
|
+
|
|
5
|
+
## 问题
|
|
6
|
+
|
|
7
|
+
PTC 模式下,`dsh-ptc-runtime-node` 用一条墙钟预算覆盖整个 `run_code` 程序,嵌套的工具调用与审批等待都算在里面:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
const wallTimer = spec.timeoutMs === null ? void 0 : setTimeout(() => {
|
|
11
|
+
timedOut = true
|
|
12
|
+
controller.abort("execution deadline reached")
|
|
13
|
+
}, spec.timeoutMs)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
于是「等你回答问题」也烧预算。本项目把这个计时器接管过来,只在没有人类交互等待时计费。
|
|
17
|
+
|
|
18
|
+
## 做法
|
|
19
|
+
|
|
20
|
+
1. 包装 `ptcRuntime.run`:把 deadline 移进插件,由插件自己计时并可暂停。
|
|
21
|
+
2. 包装三个人类等待点:`userQuestions.ask`、`userQuestions.askTimed`、`approval.request`。只有这三类调用会产生「等待人」的时间。
|
|
22
|
+
3. 用 `node:async_hooks` 的 `AsyncLocalStorage` 把计费限定在真正在等待的那次运行上,只在没有等待挂起时扣预算。
|
|
23
|
+
|
|
24
|
+
单个 `run_code` 调用仍可自带 `timeoutMs`,它继续作为执行预算生效。
|
|
25
|
+
|
|
26
|
+
## 配置
|
|
27
|
+
|
|
28
|
+
行内默认值够用;要改就在 profile 的 `cordis.patch.yml` 里覆盖:
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
- id: human-wait
|
|
32
|
+
name: 'dsh-human-wait'
|
|
33
|
+
config:
|
|
34
|
+
enabled: true
|
|
35
|
+
humanWaitCapMs: 86400000 # 24 小时;-1 = 不设上限
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| 选项 | 默认 | 含义 |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `enabled` | `true` | 暂停总开关。`false` 完全回到宿主原生行为 |
|
|
41
|
+
| `humanWaitCapMs` | `86400000` | 单次运行等待人类的最长时间,`-1` 表示不限 |
|
|
42
|
+
|
|
43
|
+
两个选项都是 **volatile**:插件运行期间会被重新读取,改完约 1.5 秒生效,不用重启。手动改补丁则在下次启动时读取。
|
|
44
|
+
|
|
45
|
+
## 设置页
|
|
46
|
+
|
|
47
|
+
`client.js` 是手写的浏览器半边,注册在 `plugins.bundle.config` 槽(key `dsh-human-wait`):一个 `humanWaitCapMs` 数字框、1 小时 / 24 小时 / 无限三个快捷按钮、字段级「已覆盖」与「恢复默认」,卡片上还有一行摘要。`enabled` 留在补丁层。
|
|
48
|
+
|
|
49
|
+
## 行为说明
|
|
50
|
+
|
|
51
|
+
- **Fail-open。** 缺少 `ptcRuntime` / `userQuestions` / `approval`、没有 `AbortSignal.any`、或者包装装不上时,插件只打一条警告,宿主原有 deadline 原样保留。
|
|
52
|
+
- **卸载时恢复。** 插件卸载(或被停用后重启)时每个包装都会被拆掉,宿主原方法恢复。
|
|
53
|
+
- **错误 kind 会变。** 暂停后的执行预算耗尽时,运行时报告 `kind: "abort"` 而不是 `"timeout"`,因为插件是通过自己的信号中止的;消息文本里仍有 `execution deadline reached (<budget>ms)`。
|
|
54
|
+
- **只影响 PTC 会话。** 标准会话本来就没有这个超时。
|
|
55
|
+
- **不是公开扩展点。** 插件包装的是两个宿主服务(`ptcRuntime`、`userQuestions`)和审批服务的方法,这是当前唯一可行的实现方式;未来 DSH 版本改动这些内部实现可能让它变成空操作——代码写成这种失败是安全的,不是致命的。
|
|
56
|
+
|
|
57
|
+
## 验证环境与验收记录
|
|
58
|
+
|
|
59
|
+
验证环境:DSH desktop **0.2.1-rc.3**(`dsh-ptc-runtime-node` 0.2.1,Node 24.21)。
|
|
60
|
+
|
|
61
|
+
2026-10-26 在 `dsh-human-wait` 0.1.0 上测得(桌面 profile,PTC 模式,出厂 120 秒预算):
|
|
62
|
+
|
|
63
|
+
| 用例 | 场景 | 结果 |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| A | 嵌套 `ask_user_question`,294037 ms 后才回答 | 答案送达,运行继续,没有 `execution deadline reached` |
|
|
66
|
+
| B | `exit_plan_mode` 计划审批,313073 ms 后才批准 | 计划通过,运行继续,没有被 120 秒取消 |
|
|
67
|
+
| C | `run_code` 里 `while (true) {}` | 约 120 秒后被终止,`Error: code run failed (abort): execution deadline reached (120000ms)` |
|
|
68
|
+
|
|
69
|
+
用例 C 的 `(abort)` 是预期行为:插件用自己的信号中止,运行时因此把失败归类为 `abort` 而不是 `timeout`,消息文本不变。
|
|
70
|
+
|
|
71
|
+
## 测试
|
|
72
|
+
|
|
73
|
+
```powershell
|
|
74
|
+
npm test # node --test,无依赖
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
覆盖:两个配置项的重读、三个等待点的包装与恢复、fail-open 分支,以及浏览器半边的 bundle id / 槽位 / 页面渲染。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-human-wait",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Pauses the PTC run_code deadline while DSH is waiting for you to answer a question, review a plan or approve a request, so long human waits are no longer reported as `execution deadline reached (120000ms)`.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"locale/*.json",
|
|
34
34
|
"icon.svg",
|
|
35
35
|
"README.md",
|
|
36
|
+
"README.en.md",
|
|
37
|
+
"docs",
|
|
36
38
|
"CHANGELOG.md",
|
|
37
39
|
"LICENSE"
|
|
38
40
|
],
|