pi-quests 0.8.0 → 0.9.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 +6 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/quest/dataplane.ts +41 -10
- package/src/renderers/quests.ts +6 -3
- package/src/renderers/tools.ts +30 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.0] - 2026-09-17
|
|
6
|
+
|
|
7
|
+
- feat: quest tool results follow pi's compact view — collapsed rows show a 10-line preview with a `ctrl+o` (`app.tools.expand`) hint, expanded rows show everything
|
|
8
|
+
- fix: RangeError crash when position labels exceed the reserved column width in `formatQuestRow`
|
|
9
|
+
- fix: wrap batch add and split in a `runTransaction` guard to roll back partial state on ID exhaustion
|
|
10
|
+
|
|
5
11
|
## [0.8.0] - 2026-05-26
|
|
6
12
|
|
|
7
13
|
- feat: migrate to `@earendil-works`
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-quests
|
|
2
2
|
|
|
3
|
-
[](CHANGELOG.md)
|
|
4
4
|
[](LICENSE.md)
|
|
5
5
|
[](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent)
|
|
6
6
|
|
package/package.json
CHANGED
package/src/quest/dataplane.ts
CHANGED
|
@@ -146,6 +146,30 @@ export class QuestLog {
|
|
|
146
146
|
this.usedIds.add(id);
|
|
147
147
|
return id;
|
|
148
148
|
}
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* Runs `fn` and, if it throws, restores all mutable state to the snapshot
|
|
152
|
+
* taken beforehand. Makes multi-step mutations (batch add, split) atomic so a
|
|
153
|
+
* partial failure (e.g. ID exhaustion mid-loop) cannot leave orphaned quests,
|
|
154
|
+
* steps, history, or used IDs behind.
|
|
155
|
+
*/
|
|
156
|
+
private runTransaction<T>(fn: () => T): T {
|
|
157
|
+
const quests = [...this.quests];
|
|
158
|
+
const steps = [...this.steps];
|
|
159
|
+
const history = [...this.history];
|
|
160
|
+
const usedIds = new Set(this.usedIds);
|
|
161
|
+
const redoStack = [...this.redoStack];
|
|
162
|
+
try {
|
|
163
|
+
return fn();
|
|
164
|
+
} catch (err) {
|
|
165
|
+
this.quests = quests;
|
|
166
|
+
this.steps = steps;
|
|
167
|
+
this.history = history;
|
|
168
|
+
this.usedIds = usedIds;
|
|
169
|
+
this.redoStack = redoStack;
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
149
173
|
private undoHandlers: {
|
|
150
174
|
[K in HistoryEntry["type"]]: (entry: Extract<HistoryEntry, { type: K }>) => {
|
|
151
175
|
success: boolean;
|
|
@@ -533,11 +557,14 @@ export class QuestLog {
|
|
|
533
557
|
const parent = this.quests.find((q) => q.id === id);
|
|
534
558
|
if (!parent) throw new Error(formatNotFound(id));
|
|
535
559
|
if (parent.done) throw new Error(formatParentDoneError(id));
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
created
|
|
539
|
-
|
|
540
|
-
|
|
560
|
+
|
|
561
|
+
return this.runTransaction(() => {
|
|
562
|
+
const created: Step[] = [];
|
|
563
|
+
for (const desc of descriptions) {
|
|
564
|
+
created.push(this.addStep(desc, id));
|
|
565
|
+
}
|
|
566
|
+
return created;
|
|
567
|
+
});
|
|
541
568
|
}
|
|
542
569
|
|
|
543
570
|
toggle(id: string): Quest | Step | null | undefined {
|
|
@@ -878,12 +905,16 @@ export class QuestLog {
|
|
|
878
905
|
};
|
|
879
906
|
}
|
|
880
907
|
}
|
|
908
|
+
const descriptions = action.descriptions;
|
|
881
909
|
try {
|
|
882
|
-
const added
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
|
|
886
|
-
|
|
910
|
+
const added = this.runTransaction(() => {
|
|
911
|
+
const result: { id: string; description: string }[] = [];
|
|
912
|
+
for (const desc of descriptions) {
|
|
913
|
+
const q = this.add(desc);
|
|
914
|
+
result.push({ id: q.id, description: q.description });
|
|
915
|
+
}
|
|
916
|
+
return result;
|
|
917
|
+
});
|
|
887
918
|
|
|
888
919
|
return { success: true, message: formatBatchAddResult(added) };
|
|
889
920
|
} catch (err) {
|
package/src/renderers/quests.ts
CHANGED
|
@@ -15,8 +15,10 @@ export function formatQuestRow(
|
|
|
15
15
|
const posWidth = visibleWidth(posText);
|
|
16
16
|
const idText = `[${q.id}]`;
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
|
|
18
|
+
// Reserve width for the widest position label ("#" + up to `16 ** idLength`)
|
|
19
|
+
// and clamp so an unexpectedly wide label can never crash the renderer.
|
|
20
|
+
const posColWidth = visibleWidth(`#${16 ** idLength}`);
|
|
21
|
+
const spacing = " ".repeat(Math.max(0, posColWidth - posWidth));
|
|
20
22
|
const idStr = `${theme.fg("muted", idText)} ${theme.fg(q.done ? "dim" : "accent", `${posText}`)}${spacing}`;
|
|
21
23
|
|
|
22
24
|
// for steps, use a different marker and indent
|
|
@@ -36,8 +38,9 @@ export function formatQuestRow(
|
|
|
36
38
|
|
|
37
39
|
export function formatStepSpacerLine(theme: Theme, idLength: number): string {
|
|
38
40
|
const idTextLength = visibleWidth(`${16 ** idLength}`);
|
|
41
|
+
const posColWidth = visibleWidth(`#${16 ** idLength}`);
|
|
39
42
|
// spacing for `[id](position string)`
|
|
40
|
-
const spacerStr = `${" ".repeat(idTextLength + 2)}${" ".repeat(
|
|
43
|
+
const spacerStr = `${" ".repeat(idTextLength + 2)}${" ".repeat(posColWidth)}`;
|
|
41
44
|
|
|
42
45
|
const marker = theme.fg("muted", " │ ");
|
|
43
46
|
|
package/src/renderers/tools.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AgentToolResult, keyHint, type Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { ResolvedConfig } from "../config.js";
|
|
4
4
|
import { logger } from "../logger.js";
|
|
@@ -15,6 +15,28 @@ type QuestArgs = {
|
|
|
15
15
|
all?: boolean;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Maximum number of result lines shown while a tool row is collapsed.
|
|
20
|
+
* Matches pi's default fallback preview so quest output behaves like built-in tools.
|
|
21
|
+
*/
|
|
22
|
+
export const COLLAPSED_PREVIEW_LINES = 10;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Applies pi's standard compact-view treatment: the collapsed view shows a preview and an
|
|
26
|
+
* expand hint (`app.tools.expand`, ctrl+o by default); the expanded view shows every line.
|
|
27
|
+
*/
|
|
28
|
+
function collapseLines(lines: string[], expanded: boolean, theme: Theme): string {
|
|
29
|
+
if (expanded || lines.length <= COLLAPSED_PREVIEW_LINES) return lines.join("\n");
|
|
30
|
+
|
|
31
|
+
const displayLines = lines.slice(0, COLLAPSED_PREVIEW_LINES);
|
|
32
|
+
const remaining = lines.length - displayLines.length;
|
|
33
|
+
const hint =
|
|
34
|
+
theme.fg("muted", `... (${remaining} more lines,`) +
|
|
35
|
+
` ${keyHint("app.tools.expand", "to expand")}${theme.fg("muted", ")")}`;
|
|
36
|
+
|
|
37
|
+
return `${displayLines.join("\n")}\n${hint}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
18
40
|
export function renderQuestCall(args: QuestArgs, theme: Theme, _context: unknown): Text {
|
|
19
41
|
const id = "id" in args ? args.id : undefined;
|
|
20
42
|
logger.debug("quests:tool", "renderCall", { action: args.action, id });
|
|
@@ -88,8 +110,12 @@ export function renderQuestResult(config: ResolvedConfig) {
|
|
|
88
110
|
questsToRender.length === 0 ||
|
|
89
111
|
!Array.isArray(allQuests)
|
|
90
112
|
) {
|
|
91
|
-
const
|
|
92
|
-
|
|
113
|
+
const textLines = result.content
|
|
114
|
+
.map((c) => (c.type === "text" ? c.text : "[image]"))
|
|
115
|
+
.join("\n")
|
|
116
|
+
.split("\n")
|
|
117
|
+
.map((line) => theme.fg("text", line));
|
|
118
|
+
return new Text(collapseLines(textLines, options.expanded, theme), 0, 0);
|
|
93
119
|
}
|
|
94
120
|
|
|
95
121
|
const quests = allQuests;
|
|
@@ -134,6 +160,6 @@ export function renderQuestResult(config: ResolvedConfig) {
|
|
|
134
160
|
}
|
|
135
161
|
}
|
|
136
162
|
|
|
137
|
-
return new Text(lines.
|
|
163
|
+
return new Text(collapseLines(lines, options.expanded, theme), 0, 0);
|
|
138
164
|
};
|
|
139
165
|
}
|