pi-mono-all 1.0.0 → 1.1.0
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 +16 -0
- package/node_modules/pi-common/package.json +2 -2
- package/node_modules/pi-common/src/auth-config.ts +2 -2
- package/node_modules/pi-mono-ask-user-question/CHANGELOG.md +9 -1
- package/node_modules/pi-mono-ask-user-question/README.md +3 -3
- package/node_modules/pi-mono-ask-user-question/index.ts +20 -12
- package/node_modules/pi-mono-ask-user-question/package.json +5 -5
- package/node_modules/pi-mono-auto-fix/CHANGELOG.md +8 -0
- package/node_modules/pi-mono-auto-fix/index.ts +1 -1
- package/node_modules/pi-mono-auto-fix/package.json +2 -2
- package/node_modules/pi-mono-btw/CHANGELOG.md +18 -7
- package/node_modules/pi-mono-btw/README.md +3 -2
- package/node_modules/pi-mono-btw/index.ts +136 -68
- package/node_modules/pi-mono-btw/package.json +5 -5
- package/node_modules/pi-mono-clear/CHANGELOG.md +9 -1
- package/node_modules/pi-mono-clear/index.ts +1 -1
- package/node_modules/pi-mono-clear/package.json +5 -5
- package/node_modules/pi-mono-context/CHANGELOG.md +8 -0
- package/node_modules/pi-mono-context/index.ts +3 -3
- package/node_modules/pi-mono-context/package.json +4 -4
- package/node_modules/pi-mono-context-guard/CHANGELOG.md +9 -1
- package/node_modules/pi-mono-context-guard/index.ts +6 -5
- package/node_modules/pi-mono-context-guard/package.json +2 -2
- package/node_modules/pi-mono-figma/CHANGELOG.md +8 -0
- package/node_modules/pi-mono-figma/index.ts +1 -1
- package/node_modules/pi-mono-figma/package.json +2 -2
- package/node_modules/pi-mono-figma/src/figma-schemas.ts +14 -7
- package/node_modules/pi-mono-figma/src/figma-tools.ts +1 -1
- package/node_modules/pi-mono-linear/CHANGELOG.md +8 -0
- package/node_modules/pi-mono-linear/index.ts +1 -1
- package/node_modules/pi-mono-linear/package.json +2 -2
- package/node_modules/pi-mono-linear/src/linear-tools.ts +1 -1
- package/node_modules/pi-mono-loop/CHANGELOG.md +9 -1
- package/node_modules/pi-mono-loop/index.ts +1 -1
- package/node_modules/pi-mono-loop/package.json +2 -2
- package/node_modules/pi-mono-multi-edit/CHANGELOG.md +9 -1
- package/node_modules/pi-mono-multi-edit/README.md +1 -1
- package/node_modules/pi-mono-multi-edit/__tests__/classic.test.ts +1 -1
- package/node_modules/pi-mono-multi-edit/__tests__/patch.test.ts +1 -1
- package/node_modules/pi-mono-multi-edit/index.ts +1 -1
- package/node_modules/pi-mono-multi-edit/package.json +5 -5
- package/node_modules/pi-mono-multi-edit/workspace.ts +1 -1
- package/node_modules/pi-mono-review/CHANGELOG.md +9 -1
- package/node_modules/pi-mono-review/common.ts +2 -2
- package/node_modules/pi-mono-review/index.ts +1 -1
- package/node_modules/pi-mono-review/package.json +5 -5
- package/node_modules/pi-mono-review/review-tui.ts +2 -2
- package/node_modules/pi-mono-review/review.ts +2 -2
- package/node_modules/pi-mono-review/reviewer.ts +3 -3
- package/node_modules/pi-mono-sentinel/CHANGELOG.md +9 -1
- package/node_modules/pi-mono-sentinel/guards/execution-tracker.ts +2 -2
- package/node_modules/pi-mono-sentinel/guards/output-scanner.ts +2 -2
- package/node_modules/pi-mono-sentinel/guards/permission-gate.ts +2 -2
- package/node_modules/pi-mono-sentinel/index.ts +1 -1
- package/node_modules/pi-mono-sentinel/package.json +2 -2
- package/node_modules/pi-mono-simplify/CHANGELOG.md +9 -1
- package/node_modules/pi-mono-simplify/index.ts +1 -1
- package/node_modules/pi-mono-simplify/package.json +4 -4
- package/node_modules/pi-mono-status-line/CHANGELOG.md +9 -1
- package/node_modules/pi-mono-status-line/basic.ts +3 -3
- package/node_modules/pi-mono-status-line/expert.ts +4 -4
- package/node_modules/pi-mono-status-line/index.ts +1 -1
- package/node_modules/pi-mono-status-line/package.json +5 -5
- package/node_modules/pi-mono-team-mode/CHANGELOG.md +9 -1
- package/node_modules/pi-mono-team-mode/index.ts +1 -1
- package/node_modules/pi-mono-team-mode/package.json +4 -4
- package/node_modules/pi-mono-team-mode/runtime/transient-session.ts +1 -1
- package/node_modules/pi-mono-team-mode/ui/notification-box.ts +2 -2
- package/node_modules/pi-mono-team-mode/ui/widget.ts +1 -1
- package/node_modules/pi-mono-usage/CHANGELOG.md +11 -0
- package/node_modules/pi-mono-usage/README.md +38 -0
- package/node_modules/pi-mono-usage/index.ts +1173 -0
- package/node_modules/pi-mono-usage/package.json +34 -0
- package/package.json +24 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# pi-mono-all
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
### Enhanced: all
|
|
8
|
+
|
|
9
|
+
- Bundle the new `pi-mono-usage` extension in the all-in-one package.
|
|
10
|
+
|
|
11
|
+
## 1.0.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
### Maintenance
|
|
16
|
+
|
|
17
|
+
- all-in-one package peer dependencies now target the new `@earendil-works` pi core package scope.
|
|
18
|
+
|
|
3
19
|
## 1.0.0
|
|
4
20
|
|
|
5
21
|
### Major Changes
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"./tool-result": "./src/tool-result.ts"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@
|
|
19
|
-
"@
|
|
18
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
19
|
+
"@earendil-works/pi-tui": "*",
|
|
20
20
|
"@sinclair/typebox": "*"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -3,8 +3,8 @@ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
-
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@
|
|
7
|
-
import { Key, matchesKey } from "@
|
|
6
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { Key, matchesKey } from "@earendil-works/pi-tui";
|
|
8
8
|
import { Type } from "@sinclair/typebox";
|
|
9
9
|
import { ApiError } from "./errors.js";
|
|
10
10
|
import { MissingAuthTokenError, readAuthToken, setAuthTokenOverride, type ReadAuthTokenOptions } from "./auth.js";
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# pi-mono-ask-user-question
|
|
2
2
|
|
|
3
|
+
## 1.7.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
### Maintenance
|
|
8
|
+
|
|
9
|
+
- Update pi core imports and peer dependencies to the new `@earendil-works` package scope.
|
|
10
|
+
|
|
3
11
|
## 1.7.3
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -17,7 +25,7 @@
|
|
|
17
25
|
|
|
18
26
|
### Fixed: ask-user-question
|
|
19
27
|
|
|
20
|
-
- Remove unused `StringEnum` import from `@
|
|
28
|
+
- Remove unused `StringEnum` import from `@earendil-works/pi-ai`.
|
|
21
29
|
|
|
22
30
|
## 1.7.1
|
|
23
31
|
|
|
@@ -220,7 +220,7 @@ The tool includes `promptSnippet` and `promptGuidelines` so the LLM knows when a
|
|
|
220
220
|
|
|
221
221
|
| Package | Role |
|
|
222
222
|
| ------------------------------- | ------------------------------------------------- |
|
|
223
|
-
| `@
|
|
224
|
-
| `@
|
|
225
|
-
| `@
|
|
223
|
+
| `@earendil-works/pi-coding-agent` | Extension API, theme types |
|
|
224
|
+
| `@earendil-works/pi-tui` | TUI primitives: Editor, Key, matchesKey, etc. |
|
|
225
|
+
| `@earendil-works/pi-ai` | `StringEnum` for Google-compatible enum schemas |
|
|
226
226
|
| `@sinclair/typebox` | JSON Schema definitions for tool parameters |
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
* - Esc to cancel
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import type { ExtensionAPI } from "@
|
|
22
|
-
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth, visibleWidth } from "@
|
|
21
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
22
|
+
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
23
23
|
import { Type } from "@sinclair/typebox";
|
|
24
24
|
|
|
25
25
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
@@ -63,6 +63,12 @@ interface FormResult {
|
|
|
63
63
|
cancelled: boolean;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
interface AskUserQuestionInput {
|
|
67
|
+
title?: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
questions: Question[];
|
|
70
|
+
}
|
|
71
|
+
|
|
66
72
|
// ─── Schema ──────────────────────────────────────────────────────────────────
|
|
67
73
|
|
|
68
74
|
const OptionSchema = Type.Object({
|
|
@@ -182,11 +188,12 @@ Use this tool when you need user input to proceed — for clarifying requirement
|
|
|
182
188
|
if (!ctx.hasUI) {
|
|
183
189
|
return errorResult("Error: UI not available (running in non-interactive mode)");
|
|
184
190
|
}
|
|
185
|
-
|
|
191
|
+
const input = params as AskUserQuestionInput;
|
|
192
|
+
if (!input.questions.length) {
|
|
186
193
|
return errorResult("Error: No questions provided");
|
|
187
194
|
}
|
|
188
195
|
|
|
189
|
-
const questions = normalize(
|
|
196
|
+
const questions = normalize(input.questions);
|
|
190
197
|
const isMulti = questions.length > 1;
|
|
191
198
|
const totalTabs = questions.length + (isMulti ? 1 : 0); // +1 for Submit tab
|
|
192
199
|
|
|
@@ -344,7 +351,7 @@ Use this tool when you need user input to proceed — for clarifying requirement
|
|
|
344
351
|
answers.push({ id: q.id, type: "text", value: t, wasCustom: true });
|
|
345
352
|
}
|
|
346
353
|
}
|
|
347
|
-
done({ title:
|
|
354
|
+
done({ title: input.title, questions, answers, cancelled });
|
|
348
355
|
}
|
|
349
356
|
|
|
350
357
|
// ── Editor submit (for "Other" mode) ────────────────────
|
|
@@ -549,13 +556,13 @@ Use this tool when you need user input to proceed — for clarifying requirement
|
|
|
549
556
|
hr();
|
|
550
557
|
|
|
551
558
|
// Title & description
|
|
552
|
-
if (
|
|
553
|
-
add(` ${theme.fg("accent", theme.bold(
|
|
559
|
+
if (input.title) {
|
|
560
|
+
add(` ${theme.fg("accent", theme.bold(input.title))}`);
|
|
554
561
|
}
|
|
555
|
-
if (
|
|
556
|
-
add(` ${theme.fg("muted",
|
|
562
|
+
if (input.description) {
|
|
563
|
+
add(` ${theme.fg("muted", input.description)}`);
|
|
557
564
|
}
|
|
558
|
-
if (
|
|
565
|
+
if (input.title || input.description) lines.push("");
|
|
559
566
|
|
|
560
567
|
// Tab bar (multi-question)
|
|
561
568
|
if (isMulti) {
|
|
@@ -876,8 +883,9 @@ Use this tool when you need user input to proceed — for clarifying requirement
|
|
|
876
883
|
// ── Custom rendering ─────────────────────────────────────────────
|
|
877
884
|
|
|
878
885
|
renderCall(args, theme, _context) {
|
|
879
|
-
const
|
|
880
|
-
const
|
|
886
|
+
const input = args as Partial<AskUserQuestionInput>;
|
|
887
|
+
const qs = input.questions || [];
|
|
888
|
+
const title = input.title;
|
|
881
889
|
let text = theme.fg("toolTitle", theme.bold("ask_user_question "));
|
|
882
890
|
if (title) {
|
|
883
891
|
text += theme.fg("accent", title) + " ";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mono-ask-user-question",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.4",
|
|
4
4
|
"description": "Pi extension for asking users structured interactive questions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi-extension"
|
|
8
8
|
],
|
|
9
9
|
"peerDependencies": {
|
|
10
|
-
"@
|
|
11
|
-
"@
|
|
12
|
-
"@
|
|
10
|
+
"@earendil-works/pi-ai": "*",
|
|
11
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
12
|
+
"@earendil-works/pi-tui": "*",
|
|
13
13
|
"@sinclair/typebox": "*"
|
|
14
14
|
},
|
|
15
15
|
"pi": {
|
|
@@ -26,4 +26,4 @@
|
|
|
26
26
|
"url": "https://github.com/emanuelcasco/pi-mono-extensions/issues"
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/emanuelcasco/pi-mono-extensions#readme"
|
|
29
|
-
}
|
|
29
|
+
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import type {
|
|
17
17
|
ExtensionAPI,
|
|
18
18
|
ExtensionContext,
|
|
19
|
-
} from "@
|
|
19
|
+
} from "@earendil-works/pi-coding-agent";
|
|
20
20
|
import { spawn } from "node:child_process";
|
|
21
21
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
22
22
|
import { homedir } from "node:os";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mono-auto-fix",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Pi extension that runs language-appropriate fixers (eslint, black, prettier, ...) on files touched during a turn",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi-extension"
|
|
8
8
|
],
|
|
9
9
|
"peerDependencies": {
|
|
10
|
-
"@
|
|
10
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
11
11
|
"@sinclair/typebox": "*"
|
|
12
12
|
},
|
|
13
13
|
"pi": {
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
# pi-mono-btw
|
|
2
2
|
|
|
3
|
+
## 1.7.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
### Maintenance
|
|
8
|
+
|
|
9
|
+
- Update pi core imports and peer dependencies to the new `@earendil-works` package scope.
|
|
10
|
+
|
|
11
|
+
## 1.7.3
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
### Enhanced: btw
|
|
16
|
+
|
|
17
|
+
- Show `/btw` answers in a focused, dismissible panel with `Esc`/`Enter` close, `↑`/`↓` scrolling, and `x` history clearing.
|
|
18
|
+
- Stop injecting completed `/btw` answers into the visible transcript; hide legacy `btw-answer` messages from older versions.
|
|
19
|
+
|
|
3
20
|
## 1.7.2
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
|
6
23
|
|
|
7
24
|
### Fixed: ask-user-question
|
|
8
25
|
|
|
9
|
-
- Remove unused `StringEnum` import from `@
|
|
10
|
-
|
|
26
|
+
- Remove unused `StringEnum` import from `@earendil-works/pi-ai`.
|
|
11
27
|
|
|
12
28
|
## 1.7.1
|
|
13
29
|
|
|
@@ -35,7 +51,6 @@
|
|
|
35
51
|
|
|
36
52
|
- New `intent-queue` and `model-config` suites; expanded coverage across `leader-runtime`, `team-manager`, `team-query-tool` and `formatters`.
|
|
37
53
|
|
|
38
|
-
|
|
39
54
|
## 1.7.0
|
|
40
55
|
|
|
41
56
|
### Minor Changes
|
|
@@ -118,7 +133,6 @@ Replaced the `grep` extension with a new security-focused `sentinel` extension f
|
|
|
118
133
|
- ### `multi-edit` — diverge from upstream fork
|
|
119
134
|
|
|
120
135
|
The extension was originally derived from [mitsuhiko/agent-stuff](https://github.com/mitsuhiko/agent-stuff)'s `pi-extensions/multi-edit.ts`. This release rewrites the largest unmodified subsystems so the implementation is structurally distinct from upstream while keeping the public contract intact.
|
|
121
|
-
|
|
122
136
|
- **Modularized layout** — the 953-line `index.ts` is split into purpose-scoped modules: `types.ts`, `workspace.ts`, `classic.ts`, `patch.ts`, `diff.ts`, and a slim `index.ts` (~180 lines of registration + dispatch wiring).
|
|
123
137
|
- **New patch engine** — `patch.ts` is now a recursive-descent parser over a `LineCursor` class with `indexOf`-based hunk anchoring. Hunks are stored as `{ oldBlock, newBlock }` raw strings (previously `{ oldLines[], newLines[] }` arrays), letting the applier splice content directly instead of reconstructing line arrays per apply.
|
|
124
138
|
- **Two-pass diff renderer** — `diff.ts` now walks `diffLines` parts into a typed `Entry[]` stream and makes all gutter / context-collapse decisions in a second pass, replacing the prior single-loop state-flag design.
|
|
@@ -140,20 +154,17 @@ Replaced the `grep` extension with a new security-focused `sentinel` extension f
|
|
|
140
154
|
### Minor Changes
|
|
141
155
|
|
|
142
156
|
- ### `multi-edit` — robustness improvements
|
|
143
|
-
|
|
144
157
|
- **No-op write guard**: skip file write and `context-guard:file-modified` event when new content is identical to what was last read — prevents unnecessary watcher churn
|
|
145
158
|
- **Early write-access check**: virtual workspace `checkWriteAccess` now validates real-filesystem permissions during the preflight pass so read-only files fail fast before any real file is touched
|
|
146
159
|
- **Curly-quote normalization**: new `findActualString` helper falls back to normalized quote matching (`"` / `'` ↔ `"` / `'`) when exact `oldText` search fails — the most common class of preflight mismatch
|
|
147
160
|
- **Atomic batch rollback**: `applyClassicEdits` gains a `rollbackOnError` option that restores all successfully written files when a later edit in the same batch fails
|
|
148
161
|
|
|
149
162
|
### `ask-user-question` — UX fixes
|
|
150
|
-
|
|
151
163
|
- **Reliable text capture on submit**: answer is read directly from the editor before it clears itself, fixing a race where the stored value was always empty
|
|
152
164
|
- **Unified advance logic**: `advanceTab()` and `saveOtherModeText()` helpers replace scattered single-question fast-paths — behaviour is now consistent regardless of form length
|
|
153
165
|
- **Auto-advance on Enter / Tab**: pressing Enter or Tab in any question (text, radio with "Other", checkbox with "Other") advances to the next tab without requiring a separate click
|
|
154
166
|
|
|
155
167
|
### `team-mode` — stability fixes
|
|
156
|
-
|
|
157
168
|
- **Infinite retry loop eliminated**: subprocess guard (`PI_TEAM_SUBPROCESS=1`) prevents spawned pi subprocesses from launching a ghost `LeaderRuntime` that immediately marks in-progress tasks as stalled
|
|
158
169
|
- **Stall detection grace period**: tasks updated within the last 2 × `LEADER_POLL_MS` (10 s) are skipped by `detectStalledTasks` — eliminates false positives on the spawning cycle
|
|
159
170
|
- **Circuit breaker**: tasks that stall more than `MAX_TASK_RETRIES` (3) times are permanently cancelled with a clear error signal instead of being silently re-queued
|
|
@@ -8,8 +8,9 @@ This extension adds Claude Code-style ` /btw ` behavior to pi.
|
|
|
8
8
|
- starts a separate model request immediately
|
|
9
9
|
- does not queue the question into the main agent loop
|
|
10
10
|
- does not interrupt the current task
|
|
11
|
-
- renders answers in a
|
|
12
|
-
-
|
|
11
|
+
- renders active requests and answers in a focused `/btw` panel while pi keeps working
|
|
12
|
+
- lets the user dismiss the panel with `Esc`/`Enter`, scroll long answers with `↑`/`↓`, and clear displayed history with `x`
|
|
13
|
+
- stores hidden history as custom session entries (`btw-history`) without injecting answers into the visible transcript or main agent context
|
|
13
14
|
|
|
14
15
|
## Why it is implemented this way
|
|
15
16
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { complete, type UserMessage } from "@
|
|
2
|
-
import type { ExtensionAPI, ExtensionContext, Theme } from "@
|
|
3
|
-
import {
|
|
1
|
+
import { complete, type UserMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { matchesKey, truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const BTW_HISTORY_ENTRY_TYPE = "btw-history";
|
|
6
|
+
const LEGACY_BTW_MESSAGE_TYPE = "btw-answer";
|
|
7
7
|
const COMPLETED_ITEM_TTL_MS = 90_000;
|
|
8
8
|
const MAX_TRANSCRIPT_CHARS = 14_000;
|
|
9
9
|
const MAX_TOOL_RESULT_CHARS = 800;
|
|
10
|
-
const
|
|
11
|
-
const
|
|
10
|
+
const MAX_PANEL_HISTORY_ITEMS = 5;
|
|
11
|
+
const MAX_PANEL_BODY_LINES = 18;
|
|
12
12
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
|
|
13
13
|
|
|
14
14
|
const SIDE_QUESTION_SYSTEM_PROMPT = [
|
|
@@ -58,12 +58,14 @@ type BtwRuntime = {
|
|
|
58
58
|
items: BtwItem[];
|
|
59
59
|
spinnerFrame: number;
|
|
60
60
|
requestRender?: () => void;
|
|
61
|
+
closePanel?: () => void;
|
|
62
|
+
panelOpen?: boolean;
|
|
61
63
|
spinnerTimer?: ReturnType<typeof setInterval>;
|
|
62
64
|
expiryTimer?: ReturnType<typeof setTimeout>;
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
const runtimes = new Map<string, BtwRuntime>();
|
|
66
|
-
const
|
|
68
|
+
const pendingHistory = new Map<string, BtwRecord[]>();
|
|
67
69
|
let nextItemId = 1;
|
|
68
70
|
|
|
69
71
|
function getSessionKey(ctx: ExtensionContext): string {
|
|
@@ -236,42 +238,29 @@ async function askSideQuestion(question: string, ctx: ExtensionContext): Promise
|
|
|
236
238
|
return answer || "No response received.";
|
|
237
239
|
}
|
|
238
240
|
|
|
239
|
-
function
|
|
240
|
-
if (!ctx.hasUI) return;
|
|
241
|
-
|
|
242
|
-
ctx.ui.setWidget(
|
|
243
|
-
BTW_WIDGET_ID,
|
|
244
|
-
(tui, theme) => {
|
|
245
|
-
runtime.requestRender = () => tui.requestRender();
|
|
246
|
-
return new BtwWidget(theme, runtime);
|
|
247
|
-
},
|
|
248
|
-
{ placement: "belowEditor" },
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function persistOrQueue(pi: ExtensionAPI, ctx: ExtensionContext, record: BtwRecord) {
|
|
241
|
+
function persistOrQueueHistory(pi: ExtensionAPI, ctx: ExtensionContext, record: BtwRecord) {
|
|
253
242
|
if (ctx.isIdle()) {
|
|
254
|
-
pi.appendEntry(
|
|
243
|
+
pi.appendEntry(BTW_HISTORY_ENTRY_TYPE, record);
|
|
255
244
|
return;
|
|
256
245
|
}
|
|
257
246
|
|
|
258
247
|
const key = getSessionKey(ctx);
|
|
259
|
-
const queue =
|
|
248
|
+
const queue = pendingHistory.get(key) ?? [];
|
|
260
249
|
queue.push(record);
|
|
261
|
-
|
|
250
|
+
pendingHistory.set(key, queue);
|
|
262
251
|
}
|
|
263
252
|
|
|
264
253
|
function flushPendingForCurrentSession(pi: ExtensionAPI, ctx: ExtensionContext) {
|
|
265
254
|
if (!ctx.isIdle()) return;
|
|
266
255
|
|
|
267
256
|
const key = getSessionKey(ctx);
|
|
268
|
-
const queue =
|
|
257
|
+
const queue = pendingHistory.get(key);
|
|
269
258
|
if (!queue || queue.length === 0) return;
|
|
270
259
|
|
|
271
260
|
for (const record of queue) {
|
|
272
|
-
pi.appendEntry(
|
|
261
|
+
pi.appendEntry(BTW_HISTORY_ENTRY_TYPE, record);
|
|
273
262
|
}
|
|
274
|
-
|
|
263
|
+
pendingHistory.delete(key);
|
|
275
264
|
}
|
|
276
265
|
|
|
277
266
|
function cleanupExpiredItems(runtime: BtwRuntime) {
|
|
@@ -321,7 +310,7 @@ async function startBtw(question: string, pi: ExtensionAPI, ctx: ExtensionContex
|
|
|
321
310
|
}
|
|
322
311
|
|
|
323
312
|
const runtime = getRuntime(ctx);
|
|
324
|
-
|
|
313
|
+
ensurePanel(ctx, runtime);
|
|
325
314
|
|
|
326
315
|
const item: BtwItem = {
|
|
327
316
|
id: `btw-${nextItemId++}`,
|
|
@@ -342,7 +331,7 @@ async function startBtw(question: string, pi: ExtensionAPI, ctx: ExtensionContex
|
|
|
342
331
|
item.answer = answer;
|
|
343
332
|
item.answeredAt = new Date().toISOString();
|
|
344
333
|
item.expiresAt = Date.now() + COMPLETED_ITEM_TTL_MS;
|
|
345
|
-
|
|
334
|
+
persistOrQueueHistory(pi, ctx, {
|
|
346
335
|
question,
|
|
347
336
|
answer,
|
|
348
337
|
askedAt: item.askedAt,
|
|
@@ -355,7 +344,7 @@ async function startBtw(question: string, pi: ExtensionAPI, ctx: ExtensionContex
|
|
|
355
344
|
item.error = error instanceof Error ? error.message : String(error);
|
|
356
345
|
item.answeredAt = new Date().toISOString();
|
|
357
346
|
item.expiresAt = Date.now() + COMPLETED_ITEM_TTL_MS;
|
|
358
|
-
|
|
347
|
+
persistOrQueueHistory(pi, ctx, {
|
|
359
348
|
question,
|
|
360
349
|
error: item.error,
|
|
361
350
|
askedAt: item.askedAt,
|
|
@@ -381,56 +370,123 @@ function extractBtwQuestion(text: string): string | null {
|
|
|
381
370
|
return match[1]?.trim() ?? "";
|
|
382
371
|
}
|
|
383
372
|
|
|
384
|
-
|
|
373
|
+
function indentWrapped(text: string, width: number, indent = " "): string[] {
|
|
374
|
+
const bodyWidth = Math.max(12, width - indent.length);
|
|
375
|
+
return text.split("\n").flatMap((line) => {
|
|
376
|
+
const wrapped = wrapTextWithAnsi(line.length > 0 ? line : " ", bodyWidth);
|
|
377
|
+
return wrapped.map((part) => indent + part);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function ensurePanel(ctx: ExtensionContext, runtime: BtwRuntime) {
|
|
382
|
+
if (!ctx.hasUI || runtime.panelOpen) {
|
|
383
|
+
runtime.requestRender?.();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
runtime.panelOpen = true;
|
|
388
|
+
void ctx.ui
|
|
389
|
+
.custom<void>((tui, theme, _keybindings, done) => {
|
|
390
|
+
const close = () => done();
|
|
391
|
+
runtime.requestRender = () => tui.requestRender();
|
|
392
|
+
runtime.closePanel = close;
|
|
393
|
+
return new BtwPanel(theme, runtime, () => tui.requestRender(), close);
|
|
394
|
+
})
|
|
395
|
+
.finally(() => {
|
|
396
|
+
runtime.panelOpen = false;
|
|
397
|
+
runtime.closePanel = undefined;
|
|
398
|
+
runtime.requestRender = undefined;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
class BtwPanel {
|
|
403
|
+
private scrollOffset = 0;
|
|
404
|
+
|
|
385
405
|
constructor(
|
|
386
406
|
private readonly theme: Theme,
|
|
387
407
|
private readonly runtime: BtwRuntime,
|
|
408
|
+
private readonly requestRender: () => void,
|
|
409
|
+
private readonly close: () => void,
|
|
388
410
|
) {}
|
|
389
411
|
|
|
412
|
+
handleInput(data: string): void {
|
|
413
|
+
if (matchesKey(data, "escape") || matchesKey(data, "enter") || matchesKey(data, "return")) {
|
|
414
|
+
this.close();
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (data === "x" || data === "X" || matchesKey(data, "x")) {
|
|
419
|
+
this.runtime.items = this.runtime.items.filter((item) => item.state === "loading");
|
|
420
|
+
this.scrollOffset = 0;
|
|
421
|
+
syncRuntimeTimers(this.runtime);
|
|
422
|
+
if (this.runtime.items.length === 0) {
|
|
423
|
+
this.close();
|
|
424
|
+
} else {
|
|
425
|
+
this.requestRender();
|
|
426
|
+
}
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (matchesKey(data, "up")) {
|
|
431
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
432
|
+
this.requestRender();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (matchesKey(data, "down")) {
|
|
437
|
+
this.scrollOffset += 1;
|
|
438
|
+
this.requestRender();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (matchesKey(data, "home")) {
|
|
443
|
+
this.scrollOffset = 0;
|
|
444
|
+
this.requestRender();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
390
448
|
render(width: number): string[] {
|
|
391
449
|
cleanupExpiredItems(this.runtime);
|
|
392
450
|
if (this.runtime.items.length === 0) {
|
|
393
|
-
return []
|
|
451
|
+
return [this.theme.fg("dim", "No /btw history."), this.theme.fg("dim", "Esc to close")].map((line) =>
|
|
452
|
+
truncateToWidth(line, width, "...", true),
|
|
453
|
+
);
|
|
394
454
|
}
|
|
395
455
|
|
|
396
456
|
const innerWidth = Math.max(24, width);
|
|
457
|
+
const latest = this.runtime.items[0]!;
|
|
397
458
|
const lines: string[] = [];
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const questionLines = wrapTextWithAnsi(this.theme.fg("accent", `Q: ${item.question}`), innerWidth);
|
|
409
|
-
lines.push(...questionLines);
|
|
410
|
-
|
|
411
|
-
if (item.state === "loading") {
|
|
412
|
-
const frame = SPINNER_FRAMES[this.runtime.spinnerFrame] ?? SPINNER_FRAMES[0]!;
|
|
413
|
-
lines.push(this.theme.fg("warning", `${frame} Answering with ${item.model}...`));
|
|
414
|
-
} else {
|
|
415
|
-
const body = item.state === "error" ? this.theme.fg("error", item.error ?? "Unknown error") : item.answer ?? "";
|
|
416
|
-
const wrapped = body
|
|
417
|
-
.split("\n")
|
|
418
|
-
.flatMap((line) => wrapTextWithAnsi(line.length > 0 ? line : " ", innerWidth));
|
|
419
|
-
const clipped = wrapped.slice(0, MAX_RENDERED_ANSWER_LINES);
|
|
420
|
-
lines.push(...clipped);
|
|
421
|
-
if (wrapped.length > clipped.length) {
|
|
422
|
-
lines.push(this.theme.fg("dim", `... ${wrapped.length - clipped.length} more line(s)`));
|
|
423
|
-
}
|
|
424
|
-
}
|
|
459
|
+
lines.push(this.theme.fg("accent", "/btw"));
|
|
460
|
+
lines.push("");
|
|
461
|
+
|
|
462
|
+
for (const item of this.runtime.items.slice(0, MAX_PANEL_HISTORY_ITEMS).reverse()) {
|
|
463
|
+
const question = `/btw ${item.question}`;
|
|
464
|
+
const color = item.id === latest.id ? "accent" : "dim";
|
|
465
|
+
lines.push(...wrapTextWithAnsi(` ${this.theme.fg(color, question)}`, innerWidth));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
lines.push("");
|
|
425
469
|
|
|
426
|
-
|
|
470
|
+
let bodyLines: string[];
|
|
471
|
+
if (latest.state === "loading") {
|
|
472
|
+
const frame = SPINNER_FRAMES[this.runtime.spinnerFrame] ?? SPINNER_FRAMES[0]!;
|
|
473
|
+
bodyLines = [this.theme.fg("warning", ` ${frame} Answering…`)];
|
|
474
|
+
} else if (latest.state === "error") {
|
|
475
|
+
bodyLines = indentWrapped(this.theme.fg("error", latest.error ?? "Unknown error"), innerWidth);
|
|
476
|
+
} else {
|
|
477
|
+
bodyLines = indentWrapped(latest.answer ?? "No response received.", innerWidth);
|
|
427
478
|
}
|
|
428
479
|
|
|
429
|
-
|
|
430
|
-
|
|
480
|
+
const maxOffset = Math.max(0, bodyLines.length - MAX_PANEL_BODY_LINES);
|
|
481
|
+
this.scrollOffset = Math.min(this.scrollOffset, maxOffset);
|
|
482
|
+
lines.push(...bodyLines.slice(this.scrollOffset, this.scrollOffset + MAX_PANEL_BODY_LINES));
|
|
483
|
+
|
|
484
|
+
if (bodyLines.length > MAX_PANEL_BODY_LINES) {
|
|
485
|
+
lines.push(this.theme.fg("dim", ` ... ${bodyLines.length - this.scrollOffset - MAX_PANEL_BODY_LINES} more line(s)`));
|
|
431
486
|
}
|
|
432
487
|
|
|
433
|
-
lines.push(
|
|
488
|
+
lines.push("");
|
|
489
|
+
lines.push(this.theme.fg("dim", "↑/↓ to scroll · x to clear history · Enter/Esc to close"));
|
|
434
490
|
return lines.map((line) => truncateToWidth(line, width, "...", true));
|
|
435
491
|
}
|
|
436
492
|
|
|
@@ -438,8 +494,13 @@ class BtwWidget {
|
|
|
438
494
|
}
|
|
439
495
|
|
|
440
496
|
export default function (pi: ExtensionAPI) {
|
|
441
|
-
|
|
442
|
-
|
|
497
|
+
// Hide answers written by older versions of this extension. New answers are
|
|
498
|
+
// shown in the focused /btw panel only, so users can dismiss them with Esc.
|
|
499
|
+
pi.registerMessageRenderer(LEGACY_BTW_MESSAGE_TYPE, () => {
|
|
500
|
+
return { render: () => [], invalidate: () => {} };
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const onSessionStart = (_event: unknown, ctx: ExtensionContext) => {
|
|
443
504
|
flushPendingForCurrentSession(pi, ctx);
|
|
444
505
|
};
|
|
445
506
|
|
|
@@ -447,13 +508,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
447
508
|
flushPendingForCurrentSession(pi, ctx);
|
|
448
509
|
};
|
|
449
510
|
|
|
450
|
-
pi.on("session_start",
|
|
451
|
-
pi.on("
|
|
511
|
+
pi.on("session_start", onSessionStart);
|
|
512
|
+
pi.on("context", (event) => ({
|
|
513
|
+
messages: event.messages.filter(
|
|
514
|
+
(message) => !(message.role === "custom" && (message as { customType?: string }).customType === LEGACY_BTW_MESSAGE_TYPE),
|
|
515
|
+
),
|
|
516
|
+
}));
|
|
452
517
|
pi.on("agent_end", flush);
|
|
453
518
|
pi.on("session_before_switch", flush);
|
|
454
519
|
pi.on("session_before_fork", flush);
|
|
455
520
|
pi.on("session_shutdown", (_event, _ctx) => {
|
|
456
521
|
for (const runtime of runtimes.values()) {
|
|
522
|
+
runtime.closePanel?.();
|
|
457
523
|
if (runtime.spinnerTimer) clearInterval(runtime.spinnerTimer);
|
|
458
524
|
if (runtime.expiryTimer) clearTimeout(runtime.expiryTimer);
|
|
459
525
|
}
|
|
@@ -468,6 +534,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
468
534
|
return { action: "continue" as const };
|
|
469
535
|
}
|
|
470
536
|
|
|
537
|
+
flushPendingForCurrentSession(pi, ctx);
|
|
538
|
+
|
|
471
539
|
const question = extractBtwQuestion(event.text);
|
|
472
540
|
if (question === null) {
|
|
473
541
|
return { action: "continue" as const };
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mono-btw",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.4",
|
|
4
4
|
"description": "Pi extension that answers side questions while the main agent keeps running",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi-extension"
|
|
8
8
|
],
|
|
9
9
|
"peerDependencies": {
|
|
10
|
-
"@
|
|
11
|
-
"@
|
|
12
|
-
"@
|
|
10
|
+
"@earendil-works/pi-ai": "*",
|
|
11
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
12
|
+
"@earendil-works/pi-tui": "*",
|
|
13
13
|
"@sinclair/typebox": "*"
|
|
14
14
|
},
|
|
15
15
|
"pi": {
|
|
@@ -26,4 +26,4 @@
|
|
|
26
26
|
"url": "https://github.com/emanuelcasco/pi-mono-extensions/issues"
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/emanuelcasco/pi-mono-extensions#readme"
|
|
29
|
-
}
|
|
29
|
+
}
|