dsh-human-wait 0.2.1 → 0.2.3
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 +18 -0
- package/README.en.md +67 -0
- package/README.md +37 -155
- 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,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.3 — 2026-11-15
|
|
4
|
+
|
|
5
|
+
Documentation only, no behaviour change.
|
|
6
|
+
|
|
7
|
+
- The opening line states the scope and the outcome (only in PTC mode; the 120-second deadline
|
|
8
|
+
pauses while DSH waits for you) instead of saying a timeout counts down.
|
|
9
|
+
- The FAQ keeps only questions readers actually ask. The defensive "does it affect standard
|
|
10
|
+
sessions?" entry is gone: the scope already says PTC mode, so there is nothing to warn about.
|
|
11
|
+
- Plain wording throughout, including the "it still timed out" checklist and the privacy note.
|
|
12
|
+
|
|
13
|
+
## 0.2.2 — 2026-11-15
|
|
14
|
+
|
|
15
|
+
- Rewrote the README for users: when the plugin helps, what it does and does not change (a table
|
|
16
|
+
with the four cases), how to install and where the setting lives. The Chinese README is now
|
|
17
|
+
primary and `README.en.md` is the English translation.
|
|
18
|
+
- Moved the implementation notes, the acceptance record and the tests to `docs/implementation.md`
|
|
19
|
+
and `docs/implementation.en.md`.
|
|
20
|
+
|
|
3
21
|
## 0.2.1 — 2026-10-27
|
|
4
22
|
|
|
5
23
|
- Adds a browser half (`client.js`), so the plugin gets its own settings page under **Plugins**:
|
package/README.en.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# dsh-human-wait
|
|
2
|
+
|
|
3
|
+
**PTC mode only: while DSH is waiting for you — a question, a plan review, an approval — the 120-second deadline pauses, so thinking longer no longer ends the run.**
|
|
4
|
+
|
|
5
|
+
PTC mode (the agent writes a short program with `run_code` and runs it as the task) puts one 120-second deadline around the whole program, and the questions, plan reviews and approvals raised in the middle count against it. Think for three minutes and you come back to:
|
|
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 replying no longer counts against that deadline, and it waits for you up to the cap you set (24 hours by default); a runaway program is still cut off at the mark.
|
|
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 is enabled; the plugin was running when that wait happened. If any of the three is missing, it cannot pause the clock for you.
|
|
49
|
+
|
|
50
|
+
**The error kind became `abort`?** Expected. The plugin stops the run itself, so the type reads `abort` instead of `timeout`; the text still contains `execution deadline reached`.
|
|
51
|
+
|
|
52
|
+
## Privacy and cost
|
|
53
|
+
|
|
54
|
+
- Wraps three host service calls in memory only; writes no files and opens no network connections
|
|
55
|
+
- Does not modify the conversation, and does not change the program's own execution budget
|
|
56
|
+
|
|
57
|
+
## Uninstall
|
|
58
|
+
|
|
59
|
+
Disable or remove it in the plugin panel, then restart DSH; the host's original methods come back.
|
|
60
|
+
|
|
61
|
+
## Technical notes
|
|
62
|
+
|
|
63
|
+
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)
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
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,66 @@
|
|
|
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 等你回话的时候(回答问题、确认计划、批准操作),那条 120 秒的限时暂停,不会因为你想得久就超时。**
|
|
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).
|
|
39
|
-
|
|
40
|
-
A single `run_code` call can still pass its own `timeoutMs`; that number keeps working as the
|
|
41
|
-
execution budget and is what the plugin enforces.
|
|
11
|
+
问题卡还会被标成「已中断」。装上这个插件,等你回话的那段时间不再算进去;最多等到你设的上限(默认 24 小时),程序自己跑飞了照样到点就被掐断。
|
|
42
12
|
|
|
43
|
-
|
|
13
|
+
> [English](README.en.md) | 中文
|
|
44
14
|
|
|
45
|
-
|
|
46
|
-
settings page under **Plugins**. Pick one route:
|
|
15
|
+
## 什么时候用得上
|
|
47
16
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
plugin manager adds both the dependency and the bundle entry for you.
|
|
17
|
+
- 用 PTC 模式跑程序,中途要你回答问题或审批,而你想读完再答
|
|
18
|
+
- 遇到过「问题卡突然变成已中断」或者上面那条超时
|
|
51
19
|
|
|
52
|
-
|
|
53
|
-
and the bundle name, then install it:
|
|
20
|
+
## 它改什么、不改什么
|
|
54
21
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
```
|
|
22
|
+
| 情况 | 没装 | 装了 |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| 程序里提问,20 秒答完 | 正常 | 正常 |
|
|
25
|
+
| 同一个程序,4 分钟后才答 | 120 秒就被中止,问题卡显示已中断 | 继续等,答案照常送达 |
|
|
26
|
+
| 一直不答 | 120 秒中止 | 等到最长等待上限(默认 24 小时)才中止 |
|
|
27
|
+
| 程序自己死循环 10 分钟 | 120 秒中止 | 120 秒中止,行为不变 |
|
|
63
28
|
|
|
64
|
-
|
|
65
|
-
# pnpm ships with the desktop runtime
|
|
66
|
-
& "$env:USERPROFILE\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\pnpm\pnpm.cjs" install --dir "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
67
|
-
```
|
|
29
|
+
## 安装
|
|
68
30
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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.
|
|
31
|
+
1. 打开 设置 → 插件 → 市场,搜索 `dsh-human-wait`
|
|
32
|
+
2. 点安装
|
|
33
|
+
3. **完全退出 DSH,再重新打开**
|
|
74
34
|
|
|
75
|
-
##
|
|
35
|
+
## 设置
|
|
76
36
|
|
|
77
|
-
|
|
78
|
-
you want something else:
|
|
37
|
+
设置 → 插件 → 已安装 → **人机等待**:
|
|
79
38
|
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
enabled: true
|
|
85
|
-
humanWaitCapMs: 86400000 # 24 h; -1 = no cap at all
|
|
86
|
-
```
|
|
39
|
+
- **最长等待**(默认 24 小时;选「无限」表示不设上限)
|
|
40
|
+
- 快捷按钮:1 小时 / 24 小时 / 无限
|
|
41
|
+
- 字段偏离出厂值时会出现「恢复默认」
|
|
42
|
+
- 卡片上有一行摘要
|
|
87
43
|
|
|
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
|
-
```
|
|
44
|
+
改完约 1.5 秒生效,不用重启。
|
|
153
45
|
|
|
154
|
-
##
|
|
46
|
+
## 常见问题
|
|
155
47
|
|
|
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`.
|
|
48
|
+
**还是超时了?** 看三处:会话头部有 PTC 标记;插件处于启用状态;等待发生的时候插件确实在运行。三者有一个不成立,它就没法替你暂停计时。
|
|
162
49
|
|
|
163
|
-
|
|
50
|
+
**报错变成 `abort` 了?** 正常。插件是自己主动叫停的,所以类型标成 `abort` 而不是 `timeout`,提示文字里照样有 `execution deadline reached`。
|
|
164
51
|
|
|
165
|
-
|
|
166
|
-
npm test # node --test, no dependencies
|
|
167
|
-
```
|
|
52
|
+
## 隐私与开销
|
|
168
53
|
|
|
169
|
-
|
|
54
|
+
它只是在内存里接管了三处「等你回答」的等待,不写文件、不联网,也不改会话内容。卸载并重启后,DSH 原来的行为就回来了。
|
|
170
55
|
|
|
171
|
-
|
|
56
|
+
## 卸载
|
|
172
57
|
|
|
173
|
-
|
|
58
|
+
在插件面板里停用或卸载,然后重启 DSH;宿主原来的方法会恢复。
|
|
174
59
|
|
|
175
|
-
|
|
60
|
+
## 技术细节
|
|
176
61
|
|
|
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)` |
|
|
62
|
+
实现原理、两个配置项、失败时的降级行为与验收记录:[docs/implementation.md](https://github.com/SpaceRelay/dsh-subagent-control/blob/main/human-wait/docs/implementation.md)
|
|
182
63
|
|
|
183
|
-
|
|
64
|
+
## 许可
|
|
184
65
|
|
|
66
|
+
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.3",
|
|
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
|
],
|