pi-quests 0.5.1 → 0.6.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 +5 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/commands/handler.ts +9 -2
- package/src/config.ts +3 -2
- package/src/index.ts +15 -3
- package/src/renderers/commands.ts +9 -1
- package/src/renderers/status.ts +34 -0
- package/src/tools/handler.ts +2 -0
package/CHANGELOG.md
CHANGED
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/commands/handler.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { ResolvedConfig } from "../config.js";
|
|
|
8
8
|
import { logger } from "../logger.js";
|
|
9
9
|
import type { QuestAction, QuestLog } from "../quest/dataplane.js";
|
|
10
10
|
import { QUEST_ACTIONS } from "../quest/types.js";
|
|
11
|
-
import { QuestListWidget } from "../renderers/commands.js";
|
|
11
|
+
import { invalidateQuestListWidget, QuestListWidget } from "../renderers/commands.js";
|
|
12
12
|
import { CHANGELOG_PATH, getVersion } from "../version.js";
|
|
13
13
|
import { reverseChangelog } from "./changelog.js";
|
|
14
14
|
import { type ParsedArgs, parseQuestArgs } from "./parse-args.js";
|
|
@@ -73,7 +73,12 @@ export function openQuestList(
|
|
|
73
73
|
);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export function createQuestsHandler(
|
|
76
|
+
export function createQuestsHandler(
|
|
77
|
+
pi: ExtensionAPI,
|
|
78
|
+
questLog: QuestLog,
|
|
79
|
+
config: ResolvedConfig,
|
|
80
|
+
onMutate?: (ctx: ExtensionCommandContext) => void,
|
|
81
|
+
) {
|
|
77
82
|
return async function handler(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
78
83
|
logger.debug("quests:cmd", "handler", { args, hasUI: ctx.hasUI });
|
|
79
84
|
|
|
@@ -144,6 +149,8 @@ export function createQuestsHandler(pi: ExtensionAPI, questLog: QuestLog, config
|
|
|
144
149
|
const result = questLog.execute(action);
|
|
145
150
|
|
|
146
151
|
logger.debug("quests:cmd", parsed.action, { success: result.success });
|
|
152
|
+
invalidateQuestListWidget();
|
|
153
|
+
onMutate?.(ctx);
|
|
147
154
|
ctx.ui.notify(result.message, result.success ? "info" : "error");
|
|
148
155
|
return;
|
|
149
156
|
}
|
package/src/config.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { getAgentDir } from "@mariozechner/pi-coding-agent";
|
|
|
5
5
|
|
|
6
6
|
export interface ResolvedConfig {
|
|
7
7
|
ids: { length: number };
|
|
8
|
-
display: { pageSize: number; progressBarMaxWidth: number };
|
|
8
|
+
display: { pageSize: number; progressBarMaxWidth: number; icon: string };
|
|
9
9
|
nudges: {
|
|
10
10
|
toolCallThreshold: number;
|
|
11
11
|
hintIntervalMinutes: number;
|
|
@@ -35,7 +35,7 @@ export const DEFAULT_FAKE_DONE_PATTERN = String.raw`\s[-\u2013\u2014]\s*(DONE|CO
|
|
|
35
35
|
|
|
36
36
|
export const DEFAULT_CONFIG: ResolvedConfig = {
|
|
37
37
|
ids: { length: 2 },
|
|
38
|
-
display: { pageSize: 10, progressBarMaxWidth: 24 },
|
|
38
|
+
display: { pageSize: 10, progressBarMaxWidth: 24, icon: "" },
|
|
39
39
|
nudges: {
|
|
40
40
|
toolCallThreshold: 8,
|
|
41
41
|
hintIntervalMinutes: 4,
|
|
@@ -114,6 +114,7 @@ export function getConfig(ctx: Pick<ExtensionContext, "cwd">): ResolvedConfig {
|
|
|
114
114
|
pageSize: user.display?.pageSize ?? DEFAULT_CONFIG.display.pageSize,
|
|
115
115
|
progressBarMaxWidth:
|
|
116
116
|
user.display?.progressBarMaxWidth ?? DEFAULT_CONFIG.display.progressBarMaxWidth,
|
|
117
|
+
icon: user.display?.icon ?? DEFAULT_CONFIG.display.icon,
|
|
117
118
|
},
|
|
118
119
|
nudges: {
|
|
119
120
|
toolCallThreshold: user.nudges?.toolCallThreshold ?? DEFAULT_CONFIG.nudges.toolCallThreshold,
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { QuestLog } from "./quest/dataplane.js";
|
|
|
9
9
|
import { formatQuestList } from "./quest/formatters.js";
|
|
10
10
|
import { QuestUsageTracker } from "./quest/tracker.js";
|
|
11
11
|
import { questChangelogRenderer } from "./renderers/changelog.js";
|
|
12
|
+
import { QuestStatusWidget } from "./renderers/status.js";
|
|
12
13
|
import { registerQuestTool } from "./tools/handler.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -25,6 +26,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
25
26
|
let questLog = new QuestLog();
|
|
26
27
|
let tracker = new QuestUsageTracker(DEFAULT_CONFIG);
|
|
27
28
|
let config: ResolvedConfig = DEFAULT_CONFIG;
|
|
29
|
+
let statusWidget = new QuestStatusWidget(DEFAULT_CONFIG.display.icon);
|
|
28
30
|
|
|
29
31
|
const shortcutKey = getConfig({ cwd: process.cwd() }).shortcuts?.openQuests ?? "ctrl+shift+l";
|
|
30
32
|
logger.debug("quests:shortcut", "register", { key: shortcutKey });
|
|
@@ -47,23 +49,33 @@ export default function (pi: ExtensionAPI): void {
|
|
|
47
49
|
config = getConfig(ctx);
|
|
48
50
|
questLog = new QuestLog(config);
|
|
49
51
|
tracker = new QuestUsageTracker(config);
|
|
52
|
+
statusWidget = new QuestStatusWidget(config.display.icon);
|
|
50
53
|
questLog.reconstructFromSession(ctx);
|
|
54
|
+
statusWidget.update(questLog, ctx.ui, ctx.ui.theme);
|
|
51
55
|
|
|
52
56
|
registerQuestTool(pi, questLog, config);
|
|
53
57
|
|
|
54
|
-
const questsHandler = createQuestsHandler(pi, questLog, config)
|
|
58
|
+
const questsHandler = createQuestsHandler(pi, questLog, config, (ctx) => {
|
|
59
|
+
statusWidget.update(questLog, ctx.ui, ctx.ui.theme);
|
|
60
|
+
});
|
|
55
61
|
pi.registerCommand("quests", {
|
|
56
62
|
description: "Quest commands: /quests [help] to see usage",
|
|
57
63
|
handler: questsHandler,
|
|
58
64
|
});
|
|
59
65
|
});
|
|
60
66
|
|
|
61
|
-
pi.on("session_tree", async (_event, ctx) =>
|
|
67
|
+
pi.on("session_tree", async (_event, ctx) => {
|
|
68
|
+
questLog.reconstructFromSession(ctx);
|
|
69
|
+
statusWidget.update(questLog, ctx.ui, ctx.ui.theme);
|
|
70
|
+
});
|
|
62
71
|
|
|
63
72
|
pi.on("turn_start", async () => tracker.clearTurnNudge());
|
|
64
73
|
|
|
65
|
-
pi.on("tool_execution_end", async (event) => {
|
|
74
|
+
pi.on("tool_execution_end", async (event, ctx) => {
|
|
66
75
|
tracker.onToolExecution(event.toolName);
|
|
76
|
+
if (event.toolName === "quest") {
|
|
77
|
+
statusWidget.update(questLog, ctx.ui, ctx.ui.theme);
|
|
78
|
+
}
|
|
67
79
|
});
|
|
68
80
|
|
|
69
81
|
pi.on("context", async (event) => {
|
|
@@ -5,6 +5,12 @@ import { logger } from "../logger.js";
|
|
|
5
5
|
import type { QuestLog } from "../quest/dataplane.js";
|
|
6
6
|
import { formatQuestRow, formatStepSpacerLine } from "./quests.js";
|
|
7
7
|
|
|
8
|
+
let activeQuestListWidget: QuestListWidget | undefined;
|
|
9
|
+
|
|
10
|
+
export function invalidateQuestListWidget(): void {
|
|
11
|
+
activeQuestListWidget?.invalidate();
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
export class QuestListWidget {
|
|
9
15
|
private cachedWidth?: number;
|
|
10
16
|
private cachedLines?: string[];
|
|
@@ -17,12 +23,14 @@ export class QuestListWidget {
|
|
|
17
23
|
private readonly config: ResolvedConfig,
|
|
18
24
|
) {
|
|
19
25
|
logger.debug("quests:widget", "create", { questCount: questLog.getAll().length });
|
|
26
|
+
activeQuestListWidget = this;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
handleInput(data: string): void {
|
|
23
30
|
logger.debug("quests:widget", "handleInput", { data });
|
|
24
31
|
if (matchesKey(data, Key.escape) || data === "q" || data === "Q") {
|
|
25
32
|
logger.debug("quests:widget", "close");
|
|
33
|
+
activeQuestListWidget = undefined;
|
|
26
34
|
this.onClose();
|
|
27
35
|
return;
|
|
28
36
|
}
|
|
@@ -134,7 +142,7 @@ export class QuestListWidget {
|
|
|
134
142
|
const barWidth = Math.min(width - 4, this.config.display.progressBarMaxWidth);
|
|
135
143
|
const filled = Math.round((doneCount / total) * barWidth);
|
|
136
144
|
const empty = barWidth - filled;
|
|
137
|
-
const bar = th.fg("success", "
|
|
145
|
+
const bar = th.fg("success", "▰".repeat(filled)) + th.fg("dim", "▱".repeat(empty));
|
|
138
146
|
|
|
139
147
|
lines.push(truncateToWidth(` ${bar} ${th.fg("muted", `${doneCount}/${total}`)}`, width));
|
|
140
148
|
lines.push("");
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { QuestLog } from "../quest/dataplane.js";
|
|
3
|
+
|
|
4
|
+
export class QuestStatusWidget {
|
|
5
|
+
private readonly key = "pi-quests";
|
|
6
|
+
private readonly barWidth = 5;
|
|
7
|
+
|
|
8
|
+
constructor(private readonly icon: string) {}
|
|
9
|
+
|
|
10
|
+
update(
|
|
11
|
+
questLog: QuestLog,
|
|
12
|
+
ui: { setStatus(key: string, text: string | undefined): void },
|
|
13
|
+
theme: Theme,
|
|
14
|
+
): void {
|
|
15
|
+
const all = questLog.getAll();
|
|
16
|
+
const total = all.length;
|
|
17
|
+
const done = all.filter((q) => q.done).length;
|
|
18
|
+
|
|
19
|
+
const text = this.formatStatus(total, done, theme);
|
|
20
|
+
ui.setStatus(this.key, text);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private formatStatus(total: number, done: number, theme: Theme): string | undefined {
|
|
24
|
+
if (total === 0) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const filled = Math.round((done / total) * this.barWidth);
|
|
29
|
+
const empty = this.barWidth - filled;
|
|
30
|
+
const bar = theme.fg("success", "▰".repeat(filled)) + theme.fg("muted", "▱".repeat(empty));
|
|
31
|
+
|
|
32
|
+
return `${theme.fg("accent", this.icon)} ${bar} ${theme.fg("dim", `${done}/${total}`)}`;
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/tools/handler.ts
CHANGED
|
@@ -14,6 +14,7 @@ const SPLIT_DISPLAY_ACTIONS = [
|
|
|
14
14
|
QUEST_ACTIONS.revert,
|
|
15
15
|
] as const;
|
|
16
16
|
|
|
17
|
+
import { invalidateQuestListWidget } from "../renderers/commands.js";
|
|
17
18
|
import { renderQuestCall, renderQuestResult } from "../renderers/tools.js";
|
|
18
19
|
import { createQuestParams, type QuestParamsType } from "./params.js";
|
|
19
20
|
|
|
@@ -96,6 +97,7 @@ function runTool(
|
|
|
96
97
|
action: QuestAction,
|
|
97
98
|
): AgentToolResult<unknown> {
|
|
98
99
|
const result = questLog.execute(action);
|
|
100
|
+
invalidateQuestListWidget();
|
|
99
101
|
logger.debug("quests:tool", "execute-complete", { toolCallId, success: result.success });
|
|
100
102
|
|
|
101
103
|
const displayQuests = SPLIT_DISPLAY_ACTIONS.includes(
|