dsh-working-activity 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/LICENSE +28 -28
- package/README.md +122 -120
- package/README.zh.md +3 -1
- package/cordis.patch.yml +19 -19
- package/lib/client.js +7 -7
- package/lib/client.js.map +1 -1
- package/lib/types/index.d.ts +7 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +2 -2
- package/package.json +106 -106
- package/src/client/WorkingLine.module.css +74 -74
- package/src/client/WorkingLine.tsx +37 -37
- package/src/client/activity.ts +50 -50
- package/src/client/css-modules.d.ts +6 -6
- package/src/client/index.ts +42 -42
- package/src/events.ts +45 -45
- package/src/index.ts +217 -211
- package/src/invariant.ts +69 -69
- package/src/status.ts +501 -501
package/LICENSE
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
BSD 3-Clause License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026, chimney (ccch1mneyyy)
|
|
4
|
-
|
|
5
|
-
Redistribution and use in source and binary forms, with or without
|
|
6
|
-
modification, are permitted provided that the following conditions are met:
|
|
7
|
-
|
|
8
|
-
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
-
list of conditions and the following disclaimer.
|
|
10
|
-
|
|
11
|
-
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
-
this list of conditions and the following disclaimer in the documentation
|
|
13
|
-
and/or other materials provided with the distribution.
|
|
14
|
-
|
|
15
|
-
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
-
contributors may be used to endorse or promote products derived from
|
|
17
|
-
this software without specific prior written permission.
|
|
18
|
-
|
|
19
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
-
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
-
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
-
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, chimney (ccch1mneyyy)
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,120 +1,122 @@
|
|
|
1
|
-
# dsh-working-activity
|
|
2
|
-
|
|
3
|
-
A live "working line" for DeepSeek Harness: the model's real-time activity — playful thinking copy, the tool actually running, elapsed time, and a turn-end summary — shown while the agent works.
|
|
4
|
-
|
|
5
|
-
## What it does
|
|
6
|
-
|
|
7
|
-
Folds the durable session stream (`turn/start`, `assistant/chunk`, `tool/call`, `tool/result`, `turn/end`) plus `agent/status` into one status line, refreshed on a render tick:
|
|
8
|
-
|
|
9
|
-
- **Thinking**: short colloquial copy rotates every few seconds (`嗯…让我捋捋`, `盘一下盘一下`, `大脑转起来了`, deadpan `lol` / `hm` / `ok`), tiered when thinking runs long (30s / 1min / 5min), night-owl copy mixed in between 00:00–06:00 local time.
|
|
10
|
-
- **Tool activity**: the running tool renders as `俏皮动词 + 参数细节 + 已耗时` (`跑个命令 npm test · 12s`); failed tools show `翻车了`-style copy in the done line.
|
|
11
|
-
- **Turn summary**: on `turn/end` the line becomes `搞定 ✓ · N 工具 · 想Xs 干Ys` (thinking/tool split), with the last tool's fragment pinned for a few seconds.
|
|
12
|
-
- **Minimal mode**: `phrases: false` renders plain functional labels (`思考中 · 总1m23s`, `bash npm test · 12s`).
|
|
13
|
-
|
|
14
|
-
Two optional sinks, both off by default only when their seam is absent:
|
|
15
|
-
|
|
16
|
-
1. **TUI prompt slot** — registers the `${activity}` template value on `ctx.tuiPrompt` when the TUI is composed. Add `${activity}` to `theme.leftPrompt` to see it next to `cwd`/`model`/`context`.
|
|
17
|
-
2. **Session events** — appends log-only `activity/status` events (never surface events: the model never sees them) for Web and other UI consumers; replay ignores them.
|
|
18
|
-
|
|
19
|
-
## Installation
|
|
20
|
-
|
|
21
|
-
The package is a self-mounting bundle: it declares `dsh.bundle.patch`
|
|
22
|
-
(`cordis.patch.yml`), so the official CLI both installs it and appends it to
|
|
23
|
-
the profile's bundle layer stack:
|
|
24
|
-
|
|
25
|
-
```sh
|
|
26
|
-
dsh plugin --profile <profile> add dsh-working-activity
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
At boot, app-boot applies the bundle's patch list over the profile root and
|
|
30
|
-
the package inserts its own `working-activity` row — no manual configuration
|
|
31
|
-
required. To tune a value, override the row's config by id in the profile's
|
|
32
|
-
user layer (`$DSH_HOME/profiles/<profile>/cordis.patch.yml`) instead of
|
|
33
|
-
inserting a second same-id row:
|
|
34
|
-
|
|
35
|
-
```yaml
|
|
36
|
-
- id: working-activity
|
|
37
|
-
config:
|
|
38
|
-
publishIntervalMs: 500
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## TUI usage
|
|
42
|
-
|
|
43
|
-
Install the plugin into a profile that composes the official `dsh-tui`, then
|
|
44
|
-
add the `${activity}` slot to the left prompt template in the profile's user
|
|
45
|
-
layer (`$DSH_HOME/profiles/<profile>/cordis.patch.yml`):
|
|
46
|
-
|
|
47
|
-
```yaml
|
|
48
|
-
- id: tui
|
|
49
|
-
config:
|
|
50
|
-
theme:
|
|
51
|
-
leftPrompt: '${cwd}${git/worktree}${activity}${model}${token_meter/cache_hit_rate}${context}'
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
While a turn runs, the prompt line shows e.g. `dsh main 跑个命令 npm test · 12s deepseek-chat …`; while thinking, `嗯…让我捋捋 · 总1m23s`; after the turn, `搞定 ✓ · 4 工具 · 想12s 干11s` briefly. Without `${activity}` in the template, the plugin is inert in the TUI (the slot is unregistered values are omitted by the template renderer).
|
|
55
|
-
|
|
56
|
-
## Web usage
|
|
57
|
-
|
|
58
|
-
The Web client renders the live line two ways, both fed by the `activity/status` events:
|
|
59
|
-
|
|
60
|
-
- The turn-level status label (`TurnStatus`, formerly the static "Deep diving...") shows the live line while a turn runs, keeping its sweep animation.
|
|
61
|
-
- A working-line dock entry (`WorkingLine` on `conversation.input.dock`) renders the turn-end statistics (token/elapsed/tool summary) above the composer after a turn settles; live phases stay on the turn label.
|
|
62
|
-
|
|
63
|
-
Both fall back to the previous static label while the plugin is absent, so the Web UI is unaffected by disabling it.
|
|
64
|
-
|
|
65
|
-
## Configuration
|
|
66
|
-
|
|
67
|
-
| Key | Type | Default | Meaning |
|
|
68
|
-
|---|---|---|---|
|
|
69
|
-
| `phrases` | `boolean` | `true` | Playful copy pool; `false` renders plain functional labels |
|
|
70
|
-
| `publish` | `boolean` | `
|
|
71
|
-
| `tickMs` | `number` | `500` | Status render tick interval (100–5000) |
|
|
72
|
-
| `publishIntervalMs` | `number` | `2000` | Minimum interval between published events while the line is stable (500–30000) |
|
|
73
|
-
| `detailLimit` | `number` | `40` | Max displayed detail length (paths/commands/patterns), 8–120 |
|
|
74
|
-
| `customActions` | `object` | `{}` | Exact tool-name → action-copy pools, e.g. `{"my_deploy": ["部署一下", "上线中"]}` |
|
|
75
|
-
| `narrate` | `boolean` | `true` | Inject the `⏵` self-narration contract into the system prompt; the line is surfaced live and stripped from the chat body |
|
|
76
|
-
|
|
77
|
-
## Event contract
|
|
78
|
-
|
|
79
|
-
`activity/status` is a log-only session event (merge-extensible `SessionEventMap` member, no `surfaceOp`):
|
|
80
|
-
|
|
81
|
-
```ts
|
|
82
|
-
{
|
|
83
|
-
phase: 'idle' | 'waiting' | 'thinking' | 'tool' | 'done'
|
|
84
|
-
line: string // plain-text status line, no ANSI
|
|
85
|
-
label?: string // current work label (tool action / stage)
|
|
86
|
-
detail?: string // path / command / pattern fragment
|
|
87
|
-
phrase?: string // current playful phrase
|
|
88
|
-
toolCount: number // tools completed this turn
|
|
89
|
-
turnElapsedMs: number // ms since turn start
|
|
90
|
-
phaseStartedAt: number // epoch ms the phase started (animation anchor)
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
- **
|
|
119
|
-
- **
|
|
120
|
-
- **
|
|
1
|
+
# dsh-working-activity
|
|
2
|
+
|
|
3
|
+
A live "working line" for DeepSeek Harness: the model's real-time activity — playful thinking copy, the tool actually running, elapsed time, and a turn-end summary — shown while the agent works.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Folds the durable session stream (`turn/start`, `assistant/chunk`, `tool/call`, `tool/result`, `turn/end`) plus `agent/status` into one status line, refreshed on a render tick:
|
|
8
|
+
|
|
9
|
+
- **Thinking**: short colloquial copy rotates every few seconds (`嗯…让我捋捋`, `盘一下盘一下`, `大脑转起来了`, deadpan `lol` / `hm` / `ok`), tiered when thinking runs long (30s / 1min / 5min), night-owl copy mixed in between 00:00–06:00 local time.
|
|
10
|
+
- **Tool activity**: the running tool renders as `俏皮动词 + 参数细节 + 已耗时` (`跑个命令 npm test · 12s`); failed tools show `翻车了`-style copy in the done line.
|
|
11
|
+
- **Turn summary**: on `turn/end` the line becomes `搞定 ✓ · N 工具 · 想Xs 干Ys` (thinking/tool split), with the last tool's fragment pinned for a few seconds.
|
|
12
|
+
- **Minimal mode**: `phrases: false` renders plain functional labels (`思考中 · 总1m23s`, `bash npm test · 12s`).
|
|
13
|
+
|
|
14
|
+
Two optional sinks, both off by default only when their seam is absent:
|
|
15
|
+
|
|
16
|
+
1. **TUI prompt slot** — registers the `${activity}` template value on `ctx.tuiPrompt` when the TUI is composed. Add `${activity}` to `theme.leftPrompt` to see it next to `cwd`/`model`/`context`.
|
|
17
|
+
2. **Session events** — appends log-only `activity/status` events (never surface events: the model never sees them) for Web and other UI consumers; replay ignores them.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
The package is a self-mounting bundle: it declares `dsh.bundle.patch`
|
|
22
|
+
(`cordis.patch.yml`), so the official CLI both installs it and appends it to
|
|
23
|
+
the profile's bundle layer stack:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
dsh plugin --profile <profile> add dsh-working-activity
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
At boot, app-boot applies the bundle's patch list over the profile root and
|
|
30
|
+
the package inserts its own `working-activity` row — no manual configuration
|
|
31
|
+
required. To tune a value, override the row's config by id in the profile's
|
|
32
|
+
user layer (`$DSH_HOME/profiles/<profile>/cordis.patch.yml`) instead of
|
|
33
|
+
inserting a second same-id row:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
- id: working-activity
|
|
37
|
+
config:
|
|
38
|
+
publishIntervalMs: 500
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## TUI usage
|
|
42
|
+
|
|
43
|
+
Install the plugin into a profile that composes the official `dsh-tui`, then
|
|
44
|
+
add the `${activity}` slot to the left prompt template in the profile's user
|
|
45
|
+
layer (`$DSH_HOME/profiles/<profile>/cordis.patch.yml`):
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
- id: tui
|
|
49
|
+
config:
|
|
50
|
+
theme:
|
|
51
|
+
leftPrompt: '${cwd}${git/worktree}${activity}${model}${token_meter/cache_hit_rate}${context}'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
While a turn runs, the prompt line shows e.g. `dsh main 跑个命令 npm test · 12s deepseek-chat …`; while thinking, `嗯…让我捋捋 · 总1m23s`; after the turn, `搞定 ✓ · 4 工具 · 想12s 干11s` briefly. Without `${activity}` in the template, the plugin is inert in the TUI (the slot is unregistered values are omitted by the template renderer).
|
|
55
|
+
|
|
56
|
+
## Web usage
|
|
57
|
+
|
|
58
|
+
The Web client renders the live line two ways, both fed by the `activity/status` events:
|
|
59
|
+
|
|
60
|
+
- The turn-level status label (`TurnStatus`, formerly the static "Deep diving...") shows the live line while a turn runs, keeping its sweep animation.
|
|
61
|
+
- A working-line dock entry (`WorkingLine` on `conversation.input.dock`) renders the turn-end statistics (token/elapsed/tool summary) above the composer after a turn settles; live phases stay on the turn label.
|
|
62
|
+
|
|
63
|
+
Both fall back to the previous static label while the plugin is absent, so the Web UI is unaffected by disabling it.
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
| Key | Type | Default | Meaning |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| `phrases` | `boolean` | `true` | Playful copy pool; `false` renders plain functional labels |
|
|
70
|
+
| `publish` | `boolean` | `false` | Append `activity/status` session events for UI consumers. Off by default: appended events currently make session logs unresumable (see note below) |
|
|
71
|
+
| `tickMs` | `number` | `500` | Status render tick interval (100–5000) |
|
|
72
|
+
| `publishIntervalMs` | `number` | `2000` | Minimum interval between published events while the line is stable (500–30000) |
|
|
73
|
+
| `detailLimit` | `number` | `40` | Max displayed detail length (paths/commands/patterns), 8–120 |
|
|
74
|
+
| `customActions` | `object` | `{}` | Exact tool-name → action-copy pools, e.g. `{"my_deploy": ["部署一下", "上线中"]}` |
|
|
75
|
+
| `narrate` | `boolean` | `true` | Inject the `⏵` self-narration contract into the system prompt; the line is surfaced live and stripped from the chat body |
|
|
76
|
+
|
|
77
|
+
## Event contract
|
|
78
|
+
|
|
79
|
+
`activity/status` is a log-only session event (merge-extensible `SessionEventMap` member, no `surfaceOp`):
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
{
|
|
83
|
+
phase: 'idle' | 'waiting' | 'thinking' | 'tool' | 'done'
|
|
84
|
+
line: string // plain-text status line, no ANSI
|
|
85
|
+
label?: string // current work label (tool action / stage)
|
|
86
|
+
detail?: string // path / command / pattern fragment
|
|
87
|
+
phrase?: string // current playful phrase
|
|
88
|
+
toolCount: number // tools completed this turn
|
|
89
|
+
turnElapsedMs: number // ms since turn start
|
|
90
|
+
phaseStartedAt: number // epoch ms the phase started (animation anchor)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> **Why `publish` is off by default:** `session.append()` cannot mark events ignorable, and the resume read path refuses any log containing unknown non-ignorable event types — so with `publish: true`, every session that rendered a status line fails to resume. Re-enable only for a log-replaying consumer on a harness that supports ignorable appends. The live TUI prompt line is unaffected.
|
|
95
|
+
|
|
96
|
+
Publishing rules: line changes publish immediately; a stable line republishes at most every `publishIntervalMs` so a long tool's elapsed time stays live without flooding the log. All data is lossless JSON; optional fields are omitted when absent.
|
|
97
|
+
|
|
98
|
+
## Export shape
|
|
99
|
+
|
|
100
|
+
A function/namespace plugin: `name` / `Config` / `apply`, no default export. The state machine (`ActivityTracker`) and copy pools live in `./status` and `./phrases` (pure, clock-injected, unit-tested). The invariant companion registers under `./invariant`.
|
|
101
|
+
|
|
102
|
+
## Model Experience
|
|
103
|
+
|
|
104
|
+
### Prompt and tool surface
|
|
105
|
+
|
|
106
|
+
Nothing. The plugin injects no prompt sections, registers no tools, and appends no surface events. `activity/status` is UI state only: it never enters derived model history, so the model cannot see its own working line.
|
|
107
|
+
|
|
108
|
+
### Token effect
|
|
109
|
+
|
|
110
|
+
Zero per request.
|
|
111
|
+
|
|
112
|
+
### KV Cache effect
|
|
113
|
+
|
|
114
|
+
No system-prompt contribution, so no cache-stability effect.
|
|
115
|
+
|
|
116
|
+
## Known Limitations and Deferred Work
|
|
117
|
+
|
|
118
|
+
- **Single active line**: the plugin keeps one status line per session; the TUI slot shows the most recently active session.
|
|
119
|
+
- **Narration is opt-in**: the `⏵` self-narration contract (model writes a short status line at the top of each reply) is injected by default (`narrate: true`); set `narrate: false` for a purely event-derived line.
|
|
120
|
+
- **No progress percentages**: DSH has no tool progress events; a long tool shows elapsed time only.
|
|
121
|
+
- **No animated frames**: the TUI slot renders a static text fragment; frame animation (moon/comet/braille presets) is deferred until the prompt-slot contract supports a frame callback.
|
|
122
|
+
- **Web dock entry duplicates the turn label**: the input-dock `WorkingLine` and the chat `TurnStatus` show the same snapshot; the dock entry exists for session views where the turn label is not visible.
|
package/README.zh.md
CHANGED
|
@@ -58,7 +58,7 @@ Web 客户端通过 `activity/status` 事件以两种方式渲染实时状态行
|
|
|
58
58
|
| 键 | 类型 | 默认值 | 含义 |
|
|
59
59
|
|---|---|---|---|
|
|
60
60
|
| `phrases` | `boolean` | `true` | 俏皮文案池;`false` 渲染朴素功能标签 |
|
|
61
|
-
| `publish` | `boolean` | `
|
|
61
|
+
| `publish` | `boolean` | `false` | 为 UI 消费者追加 `activity/status` 会话事件。默认关闭:追加的事件目前会导致会话日志无法 resume(见下方说明) |
|
|
62
62
|
| `tickMs` | `number` | `500` | 状态渲染 tick 间隔(50–5000) |
|
|
63
63
|
| `publishIntervalMs` | `number` | `2000` | 状态行稳定时两次发布事件的最小间隔(500–30000) |
|
|
64
64
|
| `detailLimit` | `number` | `40` | 详情最大展示长度(路径/命令/模式),8–120 |
|
|
@@ -82,6 +82,8 @@ Web 客户端通过 `activity/status` 事件以两种方式渲染实时状态行
|
|
|
82
82
|
}
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
> **为什么 `publish` 默认关闭:** `session.append()` 无法把事件标记为 ignorable,而 resume 的读取路径会拒绝包含未知且不可忽略事件类型的日志——开启 `publish` 后,凡是显示过状态行的会话都会 resume 失败。仅在日志重放消费者、且 harness 支持 ignorable append 时再打开。TUI 实时状态行不受影响。
|
|
86
|
+
|
|
85
87
|
发布规则:状态行变化立即发布;稳定行最多每 `publishIntervalMs` 重发一次,让长工具的已耗时保持实时而不刷爆日志。所有数据都是无损 JSON;可选字段缺省时省略。
|
|
86
88
|
|
|
87
89
|
## 导出形状
|
package/cordis.patch.yml
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
# dsh-working-activity bundle patch: mount the plugin itself into the
|
|
2
|
-
# profile composition tree.
|
|
3
|
-
#
|
|
4
|
-
# `dsh plugin --profile <name> add dsh-working-activity` auto-initializes the
|
|
5
|
-
# profile (first layer @deepseek-ai/dsh-base), pnpm-installs this package into
|
|
6
|
-
# the profile, and the CLI's reconcile step appends it to the profile's
|
|
7
|
-
# dsh.profile.bundles because this package.json declares `dsh.bundle.patch`.
|
|
8
|
-
# At boot, app-boot applies every bundle's patch list in bundle order over the
|
|
9
|
-
# empty root; this insert list is what actually mounts the working-activity
|
|
10
|
-
# row — no manual configuration is needed.
|
|
11
|
-
#
|
|
12
|
-
# All row config stays at schema defaults (phrases/publish/tickMs/
|
|
13
|
-
# publishIntervalMs/detailLimit/customActions/narrate). To tune a value (for
|
|
14
|
-
# dsh-cc, set publishIntervalMs to 500), override this row's config by id in
|
|
15
|
-
# the profile's own cordis.patch.yml user layer — do NOT insert a second
|
|
16
|
-
# same-id row.
|
|
17
|
-
- insert:
|
|
18
|
-
- id: working-activity
|
|
19
|
-
name: 'dsh-working-activity'
|
|
1
|
+
# dsh-working-activity bundle patch: mount the plugin itself into the
|
|
2
|
+
# profile composition tree.
|
|
3
|
+
#
|
|
4
|
+
# `dsh plugin --profile <name> add dsh-working-activity` auto-initializes the
|
|
5
|
+
# profile (first layer @deepseek-ai/dsh-base), pnpm-installs this package into
|
|
6
|
+
# the profile, and the CLI's reconcile step appends it to the profile's
|
|
7
|
+
# dsh.profile.bundles because this package.json declares `dsh.bundle.patch`.
|
|
8
|
+
# At boot, app-boot applies every bundle's patch list in bundle order over the
|
|
9
|
+
# empty root; this insert list is what actually mounts the working-activity
|
|
10
|
+
# row — no manual configuration is needed.
|
|
11
|
+
#
|
|
12
|
+
# All row config stays at schema defaults (phrases/publish/tickMs/
|
|
13
|
+
# publishIntervalMs/detailLimit/customActions/narrate). To tune a value (for
|
|
14
|
+
# dsh-cc, set publishIntervalMs to 500), override this row's config by id in
|
|
15
|
+
# the profile's own cordis.patch.yml user layer — do NOT insert a second
|
|
16
|
+
# same-id row.
|
|
17
|
+
- insert:
|
|
18
|
+
- id: working-activity
|
|
19
|
+
name: 'dsh-working-activity'
|
package/lib/client.js
CHANGED
|
@@ -5,8 +5,8 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
-
//#region \0dsh-css:
|
|
9
|
-
const css = ".
|
|
8
|
+
//#region \0dsh-css:D:\code\projects\dsh-working-activity\packages\activity\working-activity\src\client\WorkingLine.module.css.mjs
|
|
9
|
+
const css = ".lRI9Pa_line{box-sizing:border-box;width:100%;max-width:var(--dsh-composer-card-max-width);font-family:var(--dsw-font-family);color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;margin:0 auto;padding:4px 16px 0;font-size:13px;line-height:18px;display:flex}.lRI9Pa_marker{background:var(--dsw-alias-label-tertiary);border-radius:50%;flex:none;width:7px;height:7px;animation:1.6s ease-in-out infinite lRI9Pa_working-pulse}.lRI9Pa_line[data-activity-phase=done] .lRI9Pa_marker{background:var(--dsw-alias-state-business-primary);animation:none}.lRI9Pa_line[data-activity-phase=tool] .lRI9Pa_marker{background:var(--dsw-alias-state-business-primary)}.lRI9Pa_line[data-activity-phase=waiting] .lRI9Pa_marker{background:var(--dsw-alias-label-tertiary);opacity:.6}.lRI9Pa_text{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.lRI9Pa_tools{background:var(--dsw-alias-surface-tertiary);min-width:18px;color:var(--dsw-alias-label-secondary);text-align:center;border-radius:9px;flex:none;padding:0 5px;font-size:11px;line-height:18px}@keyframes lRI9Pa_working-pulse{0%,to{opacity:1}50%{opacity:.35}}";
|
|
10
10
|
const tagId = "dsh-working-activity/WorkingLine.module.css";
|
|
11
11
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
12
12
|
const tag = document.createElement("style");
|
|
@@ -16,11 +16,11 @@ window.__ModuleLoader__.load({
|
|
|
16
16
|
document.head.appendChild(tag);
|
|
17
17
|
}
|
|
18
18
|
var WorkingLine_module_css_default = {
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"text": "
|
|
23
|
-
"
|
|
19
|
+
"working-pulse": "lRI9Pa_working-pulse",
|
|
20
|
+
"line": "lRI9Pa_line",
|
|
21
|
+
"marker": "lRI9Pa_marker",
|
|
22
|
+
"text": "lRI9Pa_text",
|
|
23
|
+
"tools": "lRI9Pa_tools"
|
|
24
24
|
};
|
|
25
25
|
//#endregion
|
|
26
26
|
//#region src/client/WorkingLine.tsx
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["css"],"sources":["../src/client/WorkingLine.tsx","../src/client/index.ts"],"sourcesContent":["// Working-line dock entry: one dim full-width row above the composer card\n// showing the live working-activity snapshot — phase-colored breathing\n// marker, the host-composed status line, and the turn's tool count badge.\n// Renders for every live phase (waiting/thinking/tool) and the done summary;\n// hides while idle or before the first publish.\n//\n// The 'conversation.input.dock' SlotMap declaration lives in\n// @deepseek-ai/dsh-client-ui-conversation/client (contract/slots.ts); this\n// entry contributes into it without owning it.\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport css from './WorkingLine.module.css'\n\n/** Full props of the dock entry: the input-zone runtime share (session standard kit). */\nexport type WorkingLineProps = PropsRuntime<'conversation.input.dock'>\n\n/** Tool-count badge copy (no locale seat: the line text itself is host-composed). */\nconst TOOLS_LABEL = 'tools this turn'\n\n/**\n * Working-line dock entry: reads the latest activity snapshot off the\n * conversation snapshot and renders the row, or nothing when idle/absent.\n */\nexport function WorkingLine({ useSession }: WorkingLineProps) {\n const activity = useSession(s => s.activity)\n if (activity === null || activity.phase === 'idle' || activity.line === '') return null\n return (\n <div className={css.line} data-activity-phase={activity.phase}>\n <span className={css.marker} aria-hidden=\"true\" />\n <span className={css.text}>{activity.line}</span>\n {activity.toolCount > 0 && (\n <span className={css.tools} title={`${activity.toolCount} ${TOOLS_LABEL}`}>\n {activity.toolCount}\n </span>\n )}\n </div>\n )\n}\n","/**\n * Working-activity surface plugin, browser half: the working-line entry in\n * the conversation.input.dock strip. This package's node half emits\n * `activity/status` session events; the web runtime patch narrows them into\n * the conversation snapshot's `activity` member, so this dock entry reads\n * the live frame through the standard session kit and owns no store, no\n * refresh chain, and no event listener.\n *\n * Mount contract (see the root README's \"Web UI 集成\" section): the web\n * client's client-modules host scans loader entries for `dsh.client`\n * declarations and serves this package's `./client` bundle at\n * /plugins/dsh-working-activity/client.js. The entry contributes into the\n * input dock — no official-source patch is involved.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: pulls the ui-conversation SlotMap merge (the input.dock entry)\n// through the Client assembly boundary.\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { WorkingLine } from './WorkingLine.tsx'\nimport type { ActivityPhase, ActivitySnapshot } from './activity.ts'\n\nexport { WorkingLine, type WorkingLineProps } from './WorkingLine.tsx'\nexport type { ActivityPhase, ActivitySnapshot } from './activity.ts'\n\n/** Required services for the dock registration. */\nexport const inject = ['slots']\n\n/**\n * Client plugin body: the working-line dock entry.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\n name: 'conversation.input.dock',\n // The pre-slots patch used the same id in ui-conversation; entries with\n // equal order render in registration order, and goal (10) / queue (20)\n // keep their seats — this row sits between them.\n id: 'activity',\n order: 15,\n registrant: 'dsh-working-activity',\n }, WorkingLine))\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBA,MAAM,cAAc;;;;;EAMpB,SAAgB,YAAY,EAAE,cAAgC;GAC5D,MAAM,WAAW,YAAW,MAAK,EAAE,QAAQ;GAC3C,IAAI,aAAa,QAAQ,SAAS,UAAU,UAAU,SAAS,SAAS,IAAI,OAAO;GACnF,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAWA,+BAAI;IAAM,uBAAqB,SAAS;IAAxD,UAAA;KACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAQ,eAAY;KAAQ,CAAA;KACjD,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAO,UAAA,SAAS;KAAW,CAAA;KAC/C,SAAS,YAAY,KACpB,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAO,OAAO,GAAG,SAAS,UAAU,GAAG;MACzD,UAAA,SAAS;KACN,CAAA;IAEL;;EAET;;;;ECXA,MAAa,SAAS,CAAC,OAAO;;;;;EAM9B,SAAgB,MAAM,KAA0B;GAC9C,IAAI,MAAM,OAAO,iCAAiC,IAAI,MAAM,SAAS;IACnE,MAAM;IAIN,IAAI;IACJ,OAAO;IACP,YAAY;GACd,GAAG,WAAW,CAAC;EACjB"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["css"],"sources":["../src/client/WorkingLine.tsx","../src/client/index.ts"],"sourcesContent":["// Working-line dock entry: one dim full-width row above the composer card\r\n// showing the live working-activity snapshot — phase-colored breathing\r\n// marker, the host-composed status line, and the turn's tool count badge.\r\n// Renders for every live phase (waiting/thinking/tool) and the done summary;\r\n// hides while idle or before the first publish.\r\n//\r\n// The 'conversation.input.dock' SlotMap declaration lives in\r\n// @deepseek-ai/dsh-client-ui-conversation/client (contract/slots.ts); this\r\n// entry contributes into it without owning it.\r\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport css from './WorkingLine.module.css'\r\n\r\n/** Full props of the dock entry: the input-zone runtime share (session standard kit). */\r\nexport type WorkingLineProps = PropsRuntime<'conversation.input.dock'>\r\n\r\n/** Tool-count badge copy (no locale seat: the line text itself is host-composed). */\r\nconst TOOLS_LABEL = 'tools this turn'\r\n\r\n/**\r\n * Working-line dock entry: reads the latest activity snapshot off the\r\n * conversation snapshot and renders the row, or nothing when idle/absent.\r\n */\r\nexport function WorkingLine({ useSession }: WorkingLineProps) {\r\n const activity = useSession(s => s.activity)\r\n if (activity === null || activity.phase === 'idle' || activity.line === '') return null\r\n return (\r\n <div className={css.line} data-activity-phase={activity.phase}>\r\n <span className={css.marker} aria-hidden=\"true\" />\r\n <span className={css.text}>{activity.line}</span>\r\n {activity.toolCount > 0 && (\r\n <span className={css.tools} title={`${activity.toolCount} ${TOOLS_LABEL}`}>\r\n {activity.toolCount}\r\n </span>\r\n )}\r\n </div>\r\n )\r\n}\r\n","/**\r\n * Working-activity surface plugin, browser half: the working-line entry in\r\n * the conversation.input.dock strip. This package's node half emits\r\n * `activity/status` session events; the web runtime patch narrows them into\r\n * the conversation snapshot's `activity` member, so this dock entry reads\r\n * the live frame through the standard session kit and owns no store, no\r\n * refresh chain, and no event listener.\r\n *\r\n * Mount contract (see the root README's \"Web UI 集成\" section): the web\r\n * client's client-modules host scans loader entries for `dsh.client`\r\n * declarations and serves this package's `./client` bundle at\r\n * /plugins/dsh-working-activity/client.js. The entry contributes into the\r\n * input dock — no official-source patch is involved.\r\n */\r\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\r\n// Type-only: pulls the ui-conversation SlotMap merge (the input.dock entry)\r\n// through the Client assembly boundary.\r\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\r\nimport { WorkingLine } from './WorkingLine.tsx'\r\nimport type { ActivityPhase, ActivitySnapshot } from './activity.ts'\r\n\r\nexport { WorkingLine, type WorkingLineProps } from './WorkingLine.tsx'\r\nexport type { ActivityPhase, ActivitySnapshot } from './activity.ts'\r\n\r\n/** Required services for the dock registration. */\r\nexport const inject = ['slots']\r\n\r\n/**\r\n * Client plugin body: the working-line dock entry.\r\n * @param ctx - client root context.\r\n */\r\nexport function apply(ctx: ClientContext): void {\r\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\r\n name: 'conversation.input.dock',\r\n // The pre-slots patch used the same id in ui-conversation; entries with\r\n // equal order render in registration order, and goal (10) / queue (20)\r\n // keep their seats — this row sits between them.\r\n id: 'activity',\r\n order: 15,\r\n registrant: 'dsh-working-activity',\r\n }, WorkingLine))\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBA,MAAM,cAAc;;;;;EAMpB,SAAgB,YAAY,EAAE,cAAgC;GAC5D,MAAM,WAAW,YAAW,MAAK,EAAE,QAAQ;GAC3C,IAAI,aAAa,QAAQ,SAAS,UAAU,UAAU,SAAS,SAAS,IAAI,OAAO;GACnF,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAWA,+BAAI;IAAM,uBAAqB,SAAS;IAAxD,UAAA;KACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAQ,eAAY;KAAQ,CAAA;KACjD,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAO,UAAA,SAAS;KAAW,CAAA;KAC/C,SAAS,YAAY,KACpB,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAO,OAAO,GAAG,SAAS,UAAU,GAAG;MACzD,UAAA,SAAS;KACN,CAAA;IAEL;;EAET;;;;ECXA,MAAa,SAAS,CAAC,OAAO;;;;;EAM9B,SAAgB,MAAM,KAA0B;GAC9C,IAAI,MAAM,OAAO,iCAAiC,IAAI,MAAM,SAAS;IACnE,MAAM;IAIN,IAAI;IACJ,OAAO;IACP,YAAY;GACd,GAAG,WAAW,CAAC;EACjB"}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -22,7 +22,13 @@ export declare const name = "working-activity";
|
|
|
22
22
|
export type Config = {
|
|
23
23
|
/** Playful copy pool; false renders plain functional labels. */
|
|
24
24
|
phrases?: boolean;
|
|
25
|
-
/** Append `activity/status` session events for UI consumers.
|
|
25
|
+
/** Append `activity/status` session events for UI consumers. Default OFF:
|
|
26
|
+
* dsh-session's append() cannot mark events ignorable, and the resume
|
|
27
|
+
* read path refuses logs containing unknown non-ignorable types — every
|
|
28
|
+
* appended snapshot makes the whole session unresumable. Re-enable only
|
|
29
|
+
* for a log-replaying consumer on a harness that supports ignorable
|
|
30
|
+
* appends. The live status line (prompt slot / session events) is
|
|
31
|
+
* unaffected by this flag. */
|
|
26
32
|
publish?: boolean;
|
|
27
33
|
/** Status render tick interval in ms. */
|
|
28
34
|
tickMs?: number;
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAWxC,mBAAmB,aAAa,CAAA;AAEhC,eAAO,MAAM,IAAI,qBAAqB,CAAA;AAEtC,wDAAwD;AACxD,MAAM,MAAM,MAAM,GAAG;IACnB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAWxC,mBAAmB,aAAa,CAAA;AAEhC,eAAO,MAAM,IAAI,qBAAqB,CAAA;AAEtC,wDAAwD;AACxD,MAAM,MAAM,MAAM,GAAG;IACnB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;mCAM+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;GAQjB,CAAA;AAyBF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI,CAyH7D"}
|
package/lib/types/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { ActivityTracker } from './status.js';
|
|
|
19
19
|
export const name = 'working-activity';
|
|
20
20
|
export const Config = z.object({
|
|
21
21
|
phrases: z.boolean().default(true),
|
|
22
|
-
publish: z.boolean().default(
|
|
22
|
+
publish: z.boolean().default(false),
|
|
23
23
|
tickMs: z.number().step(50).min(100).max(5000).default(500),
|
|
24
24
|
publishIntervalMs: z.number().step(500).min(500).max(30_000).default(2000),
|
|
25
25
|
detailLimit: z.number().step(1).min(8).max(120).default(40),
|
|
@@ -36,7 +36,7 @@ const NARRATE_INSTRUCTION = '[状态栏] 你有一个状态栏展示给用户。
|
|
|
36
36
|
export function apply(ctx, config = {}) {
|
|
37
37
|
const resolved = {
|
|
38
38
|
phrases: config.phrases ?? true,
|
|
39
|
-
publish: config.publish ??
|
|
39
|
+
publish: config.publish ?? false,
|
|
40
40
|
tickMs: config.tickMs ?? 500,
|
|
41
41
|
publishIntervalMs: config.publishIntervalMs ?? 2000,
|
|
42
42
|
detailLimit: config.detailLimit ?? 40,
|