@duckmind/dm-darwin-arm64 0.33.2 → 0.33.4
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/dm +0 -0
- package/extensions/.dm-extensions.json +1 -34
- package/extensions/greedysearch-dm/bin/cdp.mjs +1 -1
- package/package.json +1 -1
- package/extensions/dm-ask/CHANGELOG.md +0 -149
- package/extensions/dm-ask/LICENSE +0 -21
- package/extensions/dm-ask/README.md +0 -94
- package/extensions/dm-ask/ask-ui.ts +0 -238
- package/extensions/dm-ask/ask.ts +0 -348
- package/extensions/dm-ask/btw-ui.test.ts +0 -270
- package/extensions/dm-ask/btw.command.test.ts +0 -152
- package/extensions/dm-ask/btw.test.ts +0 -340
- package/extensions/dm-ask/docs/cover.png +0 -0
- package/extensions/dm-ask/docs/cover.svg +0 -70
- package/extensions/dm-ask/docs/overlay.jpg +0 -0
- package/extensions/dm-ask/docs/vertical-cover.png +0 -0
- package/extensions/dm-ask/docs/vertical-cover.svg +0 -147
- package/extensions/dm-ask/index.ts +0 -17
- package/extensions/dm-ask/package.json +0 -44
- package/extensions/dm-ask/prompts/ask-system.txt +0 -9
package/dm
CHANGED
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"status": "ok",
|
|
3
|
-
"prepared_at": "2026-06-
|
|
3
|
+
"prepared_at": "2026-06-05T11:19:16.523784+00:00",
|
|
4
4
|
"managed_entries": [
|
|
5
5
|
{
|
|
6
6
|
"id": "dm-context",
|
|
@@ -133,30 +133,6 @@
|
|
|
133
133
|
"source_subdir": "extensions/ask-user-question",
|
|
134
134
|
"pinned_ref": "2d44b5788fbaf7aa23242226596c6d15e050beca"
|
|
135
135
|
},
|
|
136
|
-
{
|
|
137
|
-
"id": "dm-ask",
|
|
138
|
-
"upstream_name": "rpiv-btw",
|
|
139
|
-
"source_url": "https://github.com/juicesharp/rpiv-mono.git",
|
|
140
|
-
"upstream_revision": "4df925e17ebee772ec187a84f8509eaa5e72f818",
|
|
141
|
-
"target_dir": "extensions/dm-ask",
|
|
142
|
-
"bundle_mode": "source-package",
|
|
143
|
-
"copied_paths": [
|
|
144
|
-
"README.md",
|
|
145
|
-
"LICENSE",
|
|
146
|
-
"CHANGELOG.md",
|
|
147
|
-
"package.json",
|
|
148
|
-
"index.ts",
|
|
149
|
-
"btw-ui.test.ts",
|
|
150
|
-
"btw-ui.ts",
|
|
151
|
-
"btw.command.test.ts",
|
|
152
|
-
"btw.test.ts",
|
|
153
|
-
"btw.ts",
|
|
154
|
-
"prompts/",
|
|
155
|
-
"docs/"
|
|
156
|
-
],
|
|
157
|
-
"source_subdir": "packages/rpiv-btw",
|
|
158
|
-
"pinned_ref": "4df925e17ebee772ec187a84f8509eaa5e72f818"
|
|
159
|
-
},
|
|
160
136
|
{
|
|
161
137
|
"id": "dm-usage",
|
|
162
138
|
"upstream_name": "usage-extension",
|
|
@@ -341,15 +317,6 @@
|
|
|
341
317
|
"install_mode": "none",
|
|
342
318
|
"dependency_patches": []
|
|
343
319
|
},
|
|
344
|
-
{
|
|
345
|
-
"id": "dm-ask",
|
|
346
|
-
"source_dir": "extensions/dm-ask",
|
|
347
|
-
"staged_dir": "dist/extensions/dm-ask",
|
|
348
|
-
"bundle_mode": "source-package",
|
|
349
|
-
"dependencies_installed": false,
|
|
350
|
-
"install_mode": "none",
|
|
351
|
-
"dependency_patches": []
|
|
352
|
-
},
|
|
353
320
|
{
|
|
354
321
|
"id": "dm-usage",
|
|
355
322
|
"source_dir": "extensions/dm-usage",
|
|
@@ -89,7 +89,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
89
89
|
function listDaemonSockets() {
|
|
90
90
|
// Named pipes on Windows aren't enumerable as filesystem entries
|
|
91
91
|
if (platform() === "win32") return [];
|
|
92
|
-
|
|
92
|
+
const tmp = SOCKET_DIR;
|
|
93
93
|
try {
|
|
94
94
|
return readdirSync(tmp)
|
|
95
95
|
.filter((f) => f.startsWith("cdp-") && f.endsWith(".sock"))
|
package/package.json
CHANGED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to `dm-ask` are documented here.
|
|
4
|
-
|
|
5
|
-
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
-
Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [Unreleased]
|
|
9
|
-
|
|
10
|
-
## [1.5.0] - 2026-05-12
|
|
11
|
-
|
|
12
|
-
## [1.4.2] - 2026-05-11
|
|
13
|
-
|
|
14
|
-
## [1.4.1] - 2026-05-11
|
|
15
|
-
|
|
16
|
-
## [1.4.0] - 2026-05-10
|
|
17
|
-
|
|
18
|
-
## [1.3.1] - 2026-05-10
|
|
19
|
-
|
|
20
|
-
## [1.3.0] - 2026-05-08
|
|
21
|
-
|
|
22
|
-
## [1.2.1] - 2026-05-07
|
|
23
|
-
|
|
24
|
-
## [1.2.0] - 2026-05-07
|
|
25
|
-
|
|
26
|
-
## [1.1.5] - 2026-05-05
|
|
27
|
-
|
|
28
|
-
## [1.1.4] - 2026-05-03
|
|
29
|
-
|
|
30
|
-
## [1.1.3] - 2026-05-03
|
|
31
|
-
|
|
32
|
-
## [1.1.2] - 2026-05-03
|
|
33
|
-
|
|
34
|
-
## [1.1.1] - 2026-05-03
|
|
35
|
-
|
|
36
|
-
## [1.1.0] - 2026-05-03
|
|
37
|
-
|
|
38
|
-
## [1.0.19] - 2026-05-03
|
|
39
|
-
|
|
40
|
-
## [1.0.18] - 2026-05-02
|
|
41
|
-
|
|
42
|
-
## [1.0.17] - 2026-05-02
|
|
43
|
-
|
|
44
|
-
## [1.0.16] - 2026-05-02
|
|
45
|
-
|
|
46
|
-
## [1.0.15] - 2026-05-02
|
|
47
|
-
|
|
48
|
-
## [1.0.14] - 2026-05-01
|
|
49
|
-
|
|
50
|
-
### Changed
|
|
51
|
-
- Cover redesigned as a macOS-style terminal-window screenshot demonstrating the extension's hero feature.
|
|
52
|
-
|
|
53
|
-
## [1.0.13] - 2026-05-01
|
|
54
|
-
|
|
55
|
-
### Added
|
|
56
|
-
- `docs/vertical-cover.{svg,png}` — portrait-orientation hero artwork (1280×800 canvas; PNG downscaled to 320×711).
|
|
57
|
-
|
|
58
|
-
### Changed
|
|
59
|
-
- Cover canvas extended from 1280×640 to 1280×800 with refreshed crop marks/footer.
|
|
60
|
-
- README hero swapped from `docs/cover.png` to `docs/vertical-cover.png`, rendered at `width="160"`. The `<a>` wrapper around the `<picture>` was removed so the image is no longer a clickable link to the package directory.
|
|
61
|
-
|
|
62
|
-
## [1.0.12] - 2026-05-01
|
|
63
|
-
|
|
64
|
-
### Added
|
|
65
|
-
- `docs/cover.png` — package hero (rasterized from `docs/cover.svg` via `rsvg-convert`, 1280×640).
|
|
66
|
-
|
|
67
|
-
### Changed
|
|
68
|
-
- README hero: open with a `<picture>`-wrapped `cover.png` above the shield badges so DM.dev's package-card image extractor picks the friendly artwork instead of the npm version shield. Existing `docs/overlay.jpg` screenshot retained below the description.
|
|
69
|
-
|
|
70
|
-
## [1.0.11] - 2026-04-30
|
|
71
|
-
|
|
72
|
-
## [1.0.10] - 2026-04-30
|
|
73
|
-
|
|
74
|
-
## [1.0.9] - 2026-04-30
|
|
75
|
-
|
|
76
|
-
## [1.0.8] - 2026-04-29
|
|
77
|
-
|
|
78
|
-
## [1.0.7] - 2026-04-29
|
|
79
|
-
|
|
80
|
-
## [1.0.6] - 2026-04-29
|
|
81
|
-
|
|
82
|
-
## [1.0.5] - 2026-04-29
|
|
83
|
-
|
|
84
|
-
## [1.0.4] - 2026-04-28
|
|
85
|
-
|
|
86
|
-
## [1.0.3] - 2026-04-28
|
|
87
|
-
|
|
88
|
-
## [1.0.2] - 2026-04-28
|
|
89
|
-
|
|
90
|
-
## [1.0.1] - 2026-04-28
|
|
91
|
-
|
|
92
|
-
## [1.0.0] - 2026-04-28
|
|
93
|
-
|
|
94
|
-
## [0.13.0] - 2026-04-28
|
|
95
|
-
|
|
96
|
-
## [0.12.7] - 2026-04-26
|
|
97
|
-
|
|
98
|
-
## [0.12.6] - 2026-04-26
|
|
99
|
-
|
|
100
|
-
## [0.12.5] - 2026-04-24
|
|
101
|
-
|
|
102
|
-
## [0.12.4] - 2026-04-24
|
|
103
|
-
|
|
104
|
-
## [0.12.3] - 2026-04-24
|
|
105
|
-
|
|
106
|
-
## [0.12.2] - 2026-04-24
|
|
107
|
-
|
|
108
|
-
## [0.12.1] - 2026-04-24
|
|
109
|
-
|
|
110
|
-
## [0.12.0] - 2026-04-24
|
|
111
|
-
|
|
112
|
-
## [0.11.7] - 2026-04-23
|
|
113
|
-
|
|
114
|
-
## [0.11.6] - 2026-04-22
|
|
115
|
-
|
|
116
|
-
## [0.11.5] - 2026-04-22
|
|
117
|
-
|
|
118
|
-
## [0.11.4] - 2026-04-21
|
|
119
|
-
|
|
120
|
-
## [0.11.3] - 2026-04-21
|
|
121
|
-
|
|
122
|
-
## [0.11.2] - 2026-04-21
|
|
123
|
-
|
|
124
|
-
## [0.11.1] - 2026-04-20
|
|
125
|
-
|
|
126
|
-
## [0.11.0] - 2026-04-20
|
|
127
|
-
|
|
128
|
-
## [0.10.0] - 2026-04-20
|
|
129
|
-
|
|
130
|
-
## [0.9.1] - 2026-04-20
|
|
131
|
-
|
|
132
|
-
## [0.9.0] - 2026-04-19
|
|
133
|
-
|
|
134
|
-
## [0.8.3] - 2026-04-19
|
|
135
|
-
|
|
136
|
-
## [0.8.2] - 2026-04-19
|
|
137
|
-
|
|
138
|
-
## [0.8.1] - 2026-04-19
|
|
139
|
-
|
|
140
|
-
## [0.8.0] - 2026-04-19
|
|
141
|
-
|
|
142
|
-
## [0.7.0] - 2026-04-18
|
|
143
|
-
|
|
144
|
-
## [0.6.1] - 2026-04-18
|
|
145
|
-
|
|
146
|
-
## [0.6.0] — 2026-04-18
|
|
147
|
-
|
|
148
|
-
### Changed
|
|
149
|
-
- Consolidated into the `juicesharp/rpiv-mono` monorepo. Version aligned to the rpiv-pi family lockstep starting point. No runtime behavior change from `0.1.1`.
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 juicesharp
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# dm-ask
|
|
2
|
-
|
|
3
|
-
<div align="center">
|
|
4
|
-
<a href="https://github.com/juicesharp/rpiv-mono/tree/main/packages/dm-ask">
|
|
5
|
-
<picture>
|
|
6
|
-
<img src="https://raw.githubusercontent.com/juicesharp/rpiv-mono/main/packages/dm-ask/docs/cover.png" alt="dm-ask cover" width="50%">
|
|
7
|
-
</picture>
|
|
8
|
-
</a>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
[](https://www.npmjs.com/package/dm-ask)
|
|
12
|
-
[](https://opensource.org/licenses/MIT)
|
|
13
|
-
|
|
14
|
-
Ask a side question without polluting the main conversation. `dm-ask` adds `/ask <question>` to [DM Agent](https://github.com/badlogic/pi-mono) - a lightweight side agent picks up a **read-only clone** of your current conversation and answers in a panel at the bottom of the terminal. The side agent remembers its own `/ask` thread for follow-ups, while your main chat keeps going - its transcript is never polluted.
|
|
15
|
-
|
|
16
|
-

|
|
17
|
-
|
|
18
|
-
## Install
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
pi install npm:dm-ask
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Restart your DM Agent session, then type `/ask` followed by your question:
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
/ask why did we switch from sockets to SSE last week?
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Usage
|
|
31
|
-
|
|
32
|
-
### What you see
|
|
33
|
-
|
|
34
|
-
A panel opens at the bottom of the terminal with:
|
|
35
|
-
|
|
36
|
-
- your question on a banner,
|
|
37
|
-
- a `…` while the model is thinking,
|
|
38
|
-
- the answer when it arrives.
|
|
39
|
-
|
|
40
|
-
Prior `/ask` questions from the same session appear above the banner, so follow-ups have context.
|
|
41
|
-
|
|
42
|
-
### Keys
|
|
43
|
-
|
|
44
|
-
| Key | Action |
|
|
45
|
-
|---|---|
|
|
46
|
-
| `↑` / `↓` | Scroll the panel (when its content overflows) |
|
|
47
|
-
| `x` | Clear this session's `/ask` history (hidden until you have a prior entry) |
|
|
48
|
-
| `Esc` | Close the panel; cancel the request if it's still running |
|
|
49
|
-
|
|
50
|
-
### What the model sees
|
|
51
|
-
|
|
52
|
-
The side agent is a fresh, tool-less instance of the same primary model, handed a read-only clone of your current conversation. When you press enter, `/ask` sends it:
|
|
53
|
-
|
|
54
|
-
1. A snapshot of your main conversation so far - so it knows what you've been working on. The side agent only reads the clone, so nothing it does pollutes your main transcript.
|
|
55
|
-
2. Your previous `/ask` questions and answers in this session - so follow-ups make sense.
|
|
56
|
-
3. The question you just typed.
|
|
57
|
-
|
|
58
|
-
### What it does *not* do
|
|
59
|
-
|
|
60
|
-
- Your main conversation is never polluted. The side answer lives only in the panel and in memory - it's not written to the agent's transcript or to disk.
|
|
61
|
-
- `/ask` has no tools. The model answers in plain text.
|
|
62
|
-
- History is lost when you exit DM Agent. Your main session is unaffected.
|
|
63
|
-
|
|
64
|
-
## Commands
|
|
65
|
-
|
|
66
|
-
| Command | Description |
|
|
67
|
-
|---|---|
|
|
68
|
-
| `/ask <question>` | Ask a side question without polluting the main conversation |
|
|
69
|
-
|
|
70
|
-
## Architecture
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
dm-ask/
|
|
74
|
-
├── index.ts - extension entry; registers command + hooks
|
|
75
|
-
├── ask.ts - state, message threading, model call
|
|
76
|
-
├── ask-ui.ts - bottom panel renderer
|
|
77
|
-
└── prompts/
|
|
78
|
-
└── ask-system.txt - system prompt for the side call
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
DM Agent discovers the extension via `"pi": { "extensions": ["./index.ts"] }` in `package.json`.
|
|
82
|
-
|
|
83
|
-
## Troubleshooting
|
|
84
|
-
|
|
85
|
-
| Symptom | Cause | Fix |
|
|
86
|
-
|---|---|---|
|
|
87
|
-
| `/ask requires interactive mode` | Running in `pi --print …` or RPC mode | `/ask` needs a terminal - run DM interactively |
|
|
88
|
-
| `/ask requires an active model` | No primary model configured | Set one with `/login` or edit `~/.dm/agent/models.json` |
|
|
89
|
-
| Panel opens but answer never arrives | Model call failed or network dropped | Press `Esc` to cancel; check your provider credentials |
|
|
90
|
-
| History missing after restart | Expected - no disk persistence | `/ask` history is per-DM-process by design |
|
|
91
|
-
|
|
92
|
-
## License
|
|
93
|
-
|
|
94
|
-
MIT
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ask-ui — dynamic-height bottom-slot overlay for /ask.
|
|
3
|
-
*
|
|
4
|
-
* Layout (grows with content, bottom-anchored, max = terminal height):
|
|
5
|
-
* banner (theme.bg stripe, padded to width) sticky top
|
|
6
|
-
* blank
|
|
7
|
-
* history — "/ask <q>" (accent prefix + muted text), left-padded 2 cols
|
|
8
|
-
* echo — "/ask <q>" (accent prefix + muted text), left-padded 2 cols
|
|
9
|
-
* blank
|
|
10
|
-
* answer — body wrapped at width-2, left-padded 2 cols
|
|
11
|
-
* blank
|
|
12
|
-
* footer — key hints (dim) sticky bottom
|
|
13
|
-
*
|
|
14
|
-
* Natural height = fixed(5: banner, 3 blanks, footer) + 2 (echo + 1 blank before answer)
|
|
15
|
-
* + history.length + answerLines.length.
|
|
16
|
-
* DM-tui bottom-anchors the overlay so it grows upward with each /ask message.
|
|
17
|
-
* If natural height > terminal rows, we clip from the top (older history scrolls off)
|
|
18
|
-
* and ↑/↓ scroll the clip window.
|
|
19
|
-
*
|
|
20
|
-
* Keys (via matchesKey — handles ANSI + Kitty):
|
|
21
|
-
* Esc → abort in-flight call + dismiss
|
|
22
|
-
* ↑/↓ → scroll (when content exceeds terminal)
|
|
23
|
-
* x → clear current-session /ask history
|
|
24
|
-
* (f fork key deferred)
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
import type { ExtensionCommandContext, Theme } from "@mariozechner/pi-coding-agent";
|
|
28
|
-
import type { OverlayOptions } from "@mariozechner/pi-tui";
|
|
29
|
-
import {
|
|
30
|
-
type Component,
|
|
31
|
-
Key,
|
|
32
|
-
matchesKey,
|
|
33
|
-
type TUI,
|
|
34
|
-
truncateToWidth,
|
|
35
|
-
visibleWidth,
|
|
36
|
-
wrapTextWithAnsi,
|
|
37
|
-
} from "@mariozechner/pi-tui";
|
|
38
|
-
import { type AskTurn, userMessageText } from "./ask.js";
|
|
39
|
-
|
|
40
|
-
const ASK_OVERLAY_OPTIONS: OverlayOptions = {
|
|
41
|
-
anchor: "bottom-center",
|
|
42
|
-
width: "100%",
|
|
43
|
-
maxHeight: "85%",
|
|
44
|
-
margin: { left: 0, right: 0, bottom: 0 },
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const ASK_MAX_HEIGHT_RATIO = 0.85;
|
|
48
|
-
|
|
49
|
-
const SIDE_PAD = " "; // 2-col left gutter for history, echo, footer
|
|
50
|
-
const ANSWER_PAD = " "; // 4-col left gutter for answer body (double of SIDE_PAD)
|
|
51
|
-
const ASK_LITERAL = "/ask";
|
|
52
|
-
const PENDING_GLYPH = "…";
|
|
53
|
-
const FOOTER_SCROLL = "↑/↓ to scroll";
|
|
54
|
-
const FOOTER_CLEAR = "x to clear history";
|
|
55
|
-
const FOOTER_DISMISS = "Esc to dismiss";
|
|
56
|
-
const FOOTER_SEP = " · ";
|
|
57
|
-
|
|
58
|
-
type Mode = "pending" | "answer" | "error";
|
|
59
|
-
|
|
60
|
-
export interface ShowAskOverlayParams {
|
|
61
|
-
ctx: ExtensionCommandContext;
|
|
62
|
-
question: string;
|
|
63
|
-
history: AskTurn[];
|
|
64
|
-
controller: AbortController;
|
|
65
|
-
onClearHistory: () => void;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface ShowAskOverlayResult {
|
|
69
|
-
overlayPromise: Promise<void>;
|
|
70
|
-
controllerReady: Promise<AskOverlayController>;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export class AskOverlayController implements Component {
|
|
74
|
-
private mode: Mode = "pending";
|
|
75
|
-
private answer = "";
|
|
76
|
-
private error = "";
|
|
77
|
-
private scrollOffset = 0;
|
|
78
|
-
private history: AskTurn[];
|
|
79
|
-
|
|
80
|
-
constructor(
|
|
81
|
-
private readonly question: string,
|
|
82
|
-
history: AskTurn[],
|
|
83
|
-
private readonly theme: Theme,
|
|
84
|
-
private readonly tui: TUI,
|
|
85
|
-
private readonly done: (result?: undefined) => void,
|
|
86
|
-
private readonly controller: AbortController,
|
|
87
|
-
private readonly onClearHistory: () => void,
|
|
88
|
-
) {
|
|
89
|
-
this.history = [...history];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
setAnswer(text: string): void {
|
|
93
|
-
this.mode = "answer";
|
|
94
|
-
this.answer = text;
|
|
95
|
-
this.tui.requestRender();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
setError(message: string): void {
|
|
99
|
-
this.mode = "error";
|
|
100
|
-
this.error = message;
|
|
101
|
-
this.tui.requestRender();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
handleInput(data: string): void {
|
|
105
|
-
if (matchesKey(data, Key.escape)) {
|
|
106
|
-
this.controller.abort();
|
|
107
|
-
this.done();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (matchesKey(data, Key.up)) {
|
|
111
|
-
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
112
|
-
this.tui.requestRender();
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (matchesKey(data, Key.down)) {
|
|
116
|
-
this.scrollOffset = this.scrollOffset + 1;
|
|
117
|
-
this.tui.requestRender();
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (data === "x") {
|
|
121
|
-
this.history = [];
|
|
122
|
-
this.onClearHistory();
|
|
123
|
-
this.scrollOffset = 0;
|
|
124
|
-
this.tui.requestRender();
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
render(width: number): string[] {
|
|
130
|
-
const banner = this.renderBanner(width);
|
|
131
|
-
const historyLines = this.history.map((h) => this.historyLine(userMessageText(h.userMessage), width));
|
|
132
|
-
const echoLine = this.echoLine(this.question, width);
|
|
133
|
-
const answerLines = this.renderAnswer(width);
|
|
134
|
-
const footerAvail = Math.max(1, width - SIDE_PAD.length);
|
|
135
|
-
const footerParts: string[] = [];
|
|
136
|
-
if (this.mode !== "pending") footerParts.push(FOOTER_SCROLL);
|
|
137
|
-
if (this.history.length > 0) footerParts.push(FOOTER_CLEAR);
|
|
138
|
-
footerParts.push(FOOTER_DISMISS);
|
|
139
|
-
const footer =
|
|
140
|
-
SIDE_PAD + truncateToWidth(this.theme.fg("dim", footerParts.join(FOOTER_SEP)), footerAvail, "…", false);
|
|
141
|
-
|
|
142
|
-
// Natural content: banner + blank + history + echo + blank + answer + blank + footer
|
|
143
|
-
const natural: string[] = [banner, "", ...historyLines, echoLine, "", ...answerLines, "", footer];
|
|
144
|
-
|
|
145
|
-
// Clip to terminal height if we overflow. Bottom-anchor keeps footer+answer visible;
|
|
146
|
-
// ↑/↓ scrolls the top (history) up into the clipped region.
|
|
147
|
-
const termRows = (this.tui.terminal as { rows?: number }).rows ?? 24;
|
|
148
|
-
const maxRows = Math.max(4, Math.floor(termRows * ASK_MAX_HEIGHT_RATIO));
|
|
149
|
-
if (natural.length <= maxRows) {
|
|
150
|
-
return natural;
|
|
151
|
-
}
|
|
152
|
-
const excess = natural.length - maxRows;
|
|
153
|
-
if (this.scrollOffset > excess) this.scrollOffset = excess;
|
|
154
|
-
// scrollOffset=0 shows the BOTTOM (newest). Scrolling up reveals older history.
|
|
155
|
-
const start = excess - this.scrollOffset;
|
|
156
|
-
return natural.slice(start, start + maxRows);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
invalidate(): void {
|
|
160
|
-
// no-op — render recomputes from state each cycle
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private renderBanner(width: number): string {
|
|
164
|
-
const prefix = `${SIDE_PAD}${ASK_LITERAL} `;
|
|
165
|
-
const prefixWidth = visibleWidth(prefix);
|
|
166
|
-
const qAvail = Math.max(0, width - prefixWidth);
|
|
167
|
-
const qTrunc = truncateToWidth(this.question, qAvail, "…", false);
|
|
168
|
-
const raw = prefix + qTrunc;
|
|
169
|
-
const padded = raw + " ".repeat(Math.max(0, width - visibleWidth(raw)));
|
|
170
|
-
return this.theme.bg("customMessageBg", this.theme.fg("customMessageText", padded));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private historyLine(question: string, width: number): string {
|
|
174
|
-
const qAvail = Math.max(0, width - SIDE_PAD.length);
|
|
175
|
-
const qClean = question.replace(/\s+/g, " ").trim();
|
|
176
|
-
const raw = `${ASK_LITERAL} ${qClean}`;
|
|
177
|
-
const trunc = truncateToWidth(raw, qAvail, "…", false);
|
|
178
|
-
return SIDE_PAD + this.theme.fg("muted", trunc);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
private echoLine(question: string, width: number): string {
|
|
182
|
-
const bodyAvail = Math.max(1, width - SIDE_PAD.length);
|
|
183
|
-
const prefixWidth = visibleWidth(ASK_LITERAL) + 1; // "/ask "
|
|
184
|
-
const qAvail = Math.max(0, bodyAvail - prefixWidth);
|
|
185
|
-
const qClean = question.replace(/\s+/g, " ").trim();
|
|
186
|
-
const qTrunc = truncateToWidth(qClean, qAvail, "…", false);
|
|
187
|
-
return `${SIDE_PAD + this.theme.fg("accent", ASK_LITERAL)} ${this.theme.fg("muted", qTrunc)}`;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
private renderAnswer(width: number): string[] {
|
|
191
|
-
const bodyWidth = Math.max(1, width - ANSWER_PAD.length);
|
|
192
|
-
const indent = (lines: string[]) => lines.map((l) => ANSWER_PAD + l);
|
|
193
|
-
|
|
194
|
-
if (this.mode === "pending") {
|
|
195
|
-
return indent([this.theme.fg("warning", PENDING_GLYPH)]);
|
|
196
|
-
}
|
|
197
|
-
if (this.mode === "error") {
|
|
198
|
-
const out: string[] = [];
|
|
199
|
-
for (const ln of this.error.split("\n")) {
|
|
200
|
-
const src = ln.length === 0 ? " " : ln;
|
|
201
|
-
out.push(...wrapTextWithAnsi(this.theme.fg("error", src), bodyWidth));
|
|
202
|
-
}
|
|
203
|
-
return indent(out);
|
|
204
|
-
}
|
|
205
|
-
const out: string[] = [];
|
|
206
|
-
for (const ln of this.answer.split("\n")) {
|
|
207
|
-
const src = ln.length === 0 ? " " : ln;
|
|
208
|
-
out.push(...wrapTextWithAnsi(src, bodyWidth));
|
|
209
|
-
}
|
|
210
|
-
return indent(out);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export function showAskOverlay(params: ShowAskOverlayParams): ShowAskOverlayResult {
|
|
215
|
-
let resolveReady!: (controller: AskOverlayController) => void;
|
|
216
|
-
const controllerReady = new Promise<AskOverlayController>((resolve) => {
|
|
217
|
-
resolveReady = resolve;
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
const overlayPromise = params.ctx.ui.custom<void>(
|
|
221
|
-
(tui, theme, _kb, done) => {
|
|
222
|
-
const controller = new AskOverlayController(
|
|
223
|
-
params.question,
|
|
224
|
-
params.history,
|
|
225
|
-
theme,
|
|
226
|
-
tui,
|
|
227
|
-
done,
|
|
228
|
-
params.controller,
|
|
229
|
-
params.onClearHistory,
|
|
230
|
-
);
|
|
231
|
-
resolveReady(controller);
|
|
232
|
-
return controller;
|
|
233
|
-
},
|
|
234
|
-
{ overlay: true, overlayOptions: ASK_OVERLAY_OPTIONS },
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
return { overlayPromise, controllerReady };
|
|
238
|
-
}
|