pi-quests 0.5.1 → 0.6.1
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 +9 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/commands/handler.ts +9 -2
- package/src/config.ts +7 -2
- package/src/index.ts +34 -27
- package/src/quest/tracker.ts +15 -23
- package/src/renderers/commands.ts +9 -1
- package/src/renderers/status.ts +39 -0
- package/src/tools/handler.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.6.1] - 2026-04-17
|
|
6
|
+
|
|
7
|
+
- fix: make quest prompt injection static and cache-friendly with `nudges.enable` and `display.showStatus` toggles
|
|
8
|
+
|
|
9
|
+
## [0.6.0] - 2026-04-17
|
|
10
|
+
|
|
11
|
+
- feat: add footer quest progress indicator with configurable icon
|
|
12
|
+
- fix: quest list widget cache invalidation
|
|
13
|
+
|
|
5
14
|
## [0.5.1] - 2026-04-17
|
|
6
15
|
|
|
7
16
|
- fix: add context signals and calibrate nudge thresholds
|
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,8 +5,9 @@ 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; showStatus: boolean };
|
|
9
9
|
nudges: {
|
|
10
|
+
enable: boolean;
|
|
10
11
|
toolCallThreshold: number;
|
|
11
12
|
hintIntervalMinutes: number;
|
|
12
13
|
timeBasedToolCallThreshold: number;
|
|
@@ -35,8 +36,9 @@ export const DEFAULT_FAKE_DONE_PATTERN = String.raw`\s[-\u2013\u2014]\s*(DONE|CO
|
|
|
35
36
|
|
|
36
37
|
export const DEFAULT_CONFIG: ResolvedConfig = {
|
|
37
38
|
ids: { length: 2 },
|
|
38
|
-
display: { pageSize: 10, progressBarMaxWidth: 24 },
|
|
39
|
+
display: { pageSize: 10, progressBarMaxWidth: 24, icon: "", showStatus: true },
|
|
39
40
|
nudges: {
|
|
41
|
+
enable: true,
|
|
40
42
|
toolCallThreshold: 8,
|
|
41
43
|
hintIntervalMinutes: 4,
|
|
42
44
|
timeBasedToolCallThreshold: 8,
|
|
@@ -114,8 +116,11 @@ export function getConfig(ctx: Pick<ExtensionContext, "cwd">): ResolvedConfig {
|
|
|
114
116
|
pageSize: user.display?.pageSize ?? DEFAULT_CONFIG.display.pageSize,
|
|
115
117
|
progressBarMaxWidth:
|
|
116
118
|
user.display?.progressBarMaxWidth ?? DEFAULT_CONFIG.display.progressBarMaxWidth,
|
|
119
|
+
icon: user.display?.icon ?? DEFAULT_CONFIG.display.icon,
|
|
120
|
+
showStatus: user.display?.showStatus ?? DEFAULT_CONFIG.display.showStatus,
|
|
117
121
|
},
|
|
118
122
|
nudges: {
|
|
123
|
+
enable: user.nudges?.enable ?? DEFAULT_CONFIG.nudges.enable,
|
|
119
124
|
toolCallThreshold: user.nudges?.toolCallThreshold ?? DEFAULT_CONFIG.nudges.toolCallThreshold,
|
|
120
125
|
hintIntervalMinutes:
|
|
121
126
|
user.nudges?.hintIntervalMinutes ?? DEFAULT_CONFIG.nudges.hintIntervalMinutes,
|
package/src/index.ts
CHANGED
|
@@ -6,9 +6,9 @@ import { DEFAULT_CONFIG, getConfig, type ResolvedConfig } from "./config.js";
|
|
|
6
6
|
import { logger } from "./logger.js";
|
|
7
7
|
import { QUEST_PROMPT_GATE } from "./prompts.js";
|
|
8
8
|
import { QuestLog } from "./quest/dataplane.js";
|
|
9
|
-
import { formatQuestList } from "./quest/formatters.js";
|
|
10
9
|
import { QuestUsageTracker } from "./quest/tracker.js";
|
|
11
10
|
import { questChangelogRenderer } from "./renderers/changelog.js";
|
|
11
|
+
import { QuestStatusWidget } from "./renderers/status.js";
|
|
12
12
|
import { registerQuestTool } from "./tools/handler.js";
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -21,10 +21,20 @@ import { registerQuestTool } from "./tools/handler.js";
|
|
|
21
21
|
* - Snapshot relevant session state at each quest milestone.
|
|
22
22
|
* - Provide rollback support to restore a previous snapshot.
|
|
23
23
|
*/
|
|
24
|
+
const NO_QUESTS_REMINDER =
|
|
25
|
+
"No active quests. Use the quest tool to track your work. Use action: 'skill' for usage documentation.";
|
|
26
|
+
|
|
27
|
+
const FAKE_DONE_REMINDER =
|
|
28
|
+
"QUEST REMINDER: A quest has a completion marker appended to its description but is not toggled done. Use the list action to find it, then toggle it done. NEVER append completion markers via update.";
|
|
29
|
+
|
|
24
30
|
export default function (pi: ExtensionAPI): void {
|
|
25
31
|
let questLog = new QuestLog();
|
|
26
32
|
let tracker = new QuestUsageTracker(DEFAULT_CONFIG);
|
|
27
33
|
let config: ResolvedConfig = DEFAULT_CONFIG;
|
|
34
|
+
let statusWidget = new QuestStatusWidget(
|
|
35
|
+
DEFAULT_CONFIG.display.icon,
|
|
36
|
+
DEFAULT_CONFIG.display.showStatus,
|
|
37
|
+
);
|
|
28
38
|
|
|
29
39
|
const shortcutKey = getConfig({ cwd: process.cwd() }).shortcuts?.openQuests ?? "ctrl+shift+l";
|
|
30
40
|
logger.debug("quests:shortcut", "register", { key: shortcutKey });
|
|
@@ -47,26 +57,39 @@ export default function (pi: ExtensionAPI): void {
|
|
|
47
57
|
config = getConfig(ctx);
|
|
48
58
|
questLog = new QuestLog(config);
|
|
49
59
|
tracker = new QuestUsageTracker(config);
|
|
60
|
+
statusWidget = new QuestStatusWidget(config.display.icon, config.display.showStatus);
|
|
50
61
|
questLog.reconstructFromSession(ctx);
|
|
62
|
+
if (questLog.getAll().length > 0) tracker.markQuestToolUsed();
|
|
63
|
+
statusWidget.update(questLog, ctx.ui, ctx.ui.theme);
|
|
51
64
|
|
|
52
65
|
registerQuestTool(pi, questLog, config);
|
|
53
66
|
|
|
54
|
-
const questsHandler = createQuestsHandler(pi, questLog, config)
|
|
67
|
+
const questsHandler = createQuestsHandler(pi, questLog, config, (ctx) => {
|
|
68
|
+
statusWidget.update(questLog, ctx.ui, ctx.ui.theme);
|
|
69
|
+
});
|
|
55
70
|
pi.registerCommand("quests", {
|
|
56
71
|
description: "Quest commands: /quests [help] to see usage",
|
|
57
72
|
handler: questsHandler,
|
|
58
73
|
});
|
|
59
74
|
});
|
|
60
75
|
|
|
61
|
-
pi.on("session_tree", async (_event, ctx) =>
|
|
76
|
+
pi.on("session_tree", async (_event, ctx) => {
|
|
77
|
+
questLog.reconstructFromSession(ctx);
|
|
78
|
+
if (questLog.getAll().length > 0) tracker.markQuestToolUsed();
|
|
79
|
+
statusWidget.update(questLog, ctx.ui, ctx.ui.theme);
|
|
80
|
+
});
|
|
62
81
|
|
|
63
82
|
pi.on("turn_start", async () => tracker.clearTurnNudge());
|
|
64
83
|
|
|
65
|
-
pi.on("tool_execution_end", async (event) => {
|
|
84
|
+
pi.on("tool_execution_end", async (event, ctx) => {
|
|
66
85
|
tracker.onToolExecution(event.toolName);
|
|
86
|
+
if (event.toolName === "quest") {
|
|
87
|
+
statusWidget.update(questLog, ctx.ui, ctx.ui.theme);
|
|
88
|
+
}
|
|
67
89
|
});
|
|
68
90
|
|
|
69
91
|
pi.on("context", async (event) => {
|
|
92
|
+
if (!config.nudges.enable) return undefined;
|
|
70
93
|
const latestPrompt = event.messages
|
|
71
94
|
.filter((m) => m.role === "user")
|
|
72
95
|
.map((m) => (typeof m.content === "string" ? m.content : ""))
|
|
@@ -79,44 +102,28 @@ export default function (pi: ExtensionAPI): void {
|
|
|
79
102
|
const hasTopLevelQuestWithoutSubs = activeTopLevel.some(
|
|
80
103
|
(q) => !allQuests.some((step) => (step as { parentId?: string }).parentId === q.id),
|
|
81
104
|
);
|
|
82
|
-
const nudge = tracker.getNudge(
|
|
83
|
-
activeQuestCount,
|
|
84
|
-
allQuests,
|
|
85
|
-
latestPrompt,
|
|
86
|
-
hasTopLevelQuestWithoutSubs,
|
|
87
|
-
);
|
|
105
|
+
const nudge = tracker.getNudge(activeQuestCount, latestPrompt, hasTopLevelQuestWithoutSubs);
|
|
88
106
|
|
|
89
107
|
const fakeDoneRegex = new RegExp(config.validation.fakeDonePattern, "i");
|
|
90
108
|
const fakeDone = questLog.getAll().find((q) => !q.done && fakeDoneRegex.test(q.description));
|
|
91
109
|
if (!nudge && !fakeDone) return undefined;
|
|
92
110
|
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
}
|
|
111
|
+
const parts: string[] = [];
|
|
112
|
+
if (nudge) parts.push(nudge);
|
|
113
|
+
if (fakeDone) parts.push(FAKE_DONE_REMINDER);
|
|
97
114
|
|
|
98
115
|
const reminder: UserMessage = {
|
|
99
116
|
role: "user",
|
|
100
|
-
content:
|
|
117
|
+
content: parts.join("\n"),
|
|
101
118
|
timestamp: Date.now(),
|
|
102
119
|
};
|
|
103
120
|
return { messages: [...event.messages, reminder] };
|
|
104
121
|
});
|
|
105
122
|
|
|
106
123
|
pi.on("before_agent_start", async (event) => {
|
|
107
|
-
|
|
108
|
-
let reminder = "";
|
|
109
|
-
if (quests.length > 0) {
|
|
110
|
-
const remaining = quests.filter((q) => !q.done).length;
|
|
111
|
-
const list = formatQuestList(quests);
|
|
112
|
-
reminder = `Active quests (${remaining}/${quests.length}):\n${list}\n\nKeep quest progress updated as you work.`;
|
|
113
|
-
} else {
|
|
114
|
-
reminder =
|
|
115
|
-
"No active quests. Use the quest tool to track your work. Use action: 'skill' for usage documentation.";
|
|
116
|
-
}
|
|
117
|
-
|
|
124
|
+
if (tracker.hasUsedQuestTool) return undefined;
|
|
118
125
|
return {
|
|
119
|
-
systemPrompt: `# Quest Management\n${QUEST_PROMPT_GATE}${event.systemPrompt}\n\n## Quest Management\n${
|
|
126
|
+
systemPrompt: `# Quest Management\n${QUEST_PROMPT_GATE}${event.systemPrompt}\n\n## Quest Management\n${NO_QUESTS_REMINDER}`,
|
|
120
127
|
};
|
|
121
128
|
});
|
|
122
129
|
|
package/src/quest/tracker.ts
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
import type { ResolvedConfig } from "../config.js";
|
|
2
2
|
import { logger } from "../logger.js";
|
|
3
|
-
import type { Quest } from "./types.js";
|
|
4
3
|
|
|
5
4
|
const ACKNOWLEDGEMENT = "Update your quest status before continuing.";
|
|
6
5
|
|
|
7
|
-
function formatActiveQuests(allQuests: Quest[], limit = 3): string {
|
|
8
|
-
const active = allQuests.filter((q) => !q.done);
|
|
9
|
-
if (active.length === 0) return "";
|
|
10
|
-
|
|
11
|
-
const shown = active.slice(0, limit);
|
|
12
|
-
const lines = shown.map((q) => ` [${q.id}]: ${q.description}`);
|
|
13
|
-
if (active.length > limit) lines.push(` ... and ${active.length - limit} more`);
|
|
14
|
-
|
|
15
|
-
return `\nActive quests:\n${lines.join("\n")}`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
6
|
type NudgeCandidate = { index: number; message: string };
|
|
19
7
|
|
|
20
8
|
export class QuestUsageTracker {
|
|
@@ -43,9 +31,17 @@ export class QuestUsageTracker {
|
|
|
43
31
|
this.nudgedThisTurn = false;
|
|
44
32
|
}
|
|
45
33
|
|
|
34
|
+
get hasUsedQuestTool(): boolean {
|
|
35
|
+
return this.hasEverUsedQuestTool;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
markQuestToolUsed(): void {
|
|
39
|
+
this.hasEverUsedQuestTool = true;
|
|
40
|
+
this.lastQuestToolTime = Date.now();
|
|
41
|
+
}
|
|
42
|
+
|
|
46
43
|
getNudge(
|
|
47
44
|
activeQuestCount: number,
|
|
48
|
-
allQuests: Quest[],
|
|
49
45
|
latestPrompt?: string,
|
|
50
46
|
hasTopLevelQuestWithoutSubs?: boolean,
|
|
51
47
|
): string | undefined {
|
|
@@ -67,7 +63,6 @@ export class QuestUsageTracker {
|
|
|
67
63
|
|
|
68
64
|
const eligible = this.getEligibleNudges(
|
|
69
65
|
activeQuestCount,
|
|
70
|
-
allQuests,
|
|
71
66
|
latestPrompt,
|
|
72
67
|
hasTopLevelQuestWithoutSubs,
|
|
73
68
|
);
|
|
@@ -101,7 +96,6 @@ export class QuestUsageTracker {
|
|
|
101
96
|
|
|
102
97
|
private getEligibleNudges(
|
|
103
98
|
activeQuestCount: number,
|
|
104
|
-
allQuests: Quest[],
|
|
105
99
|
latestPrompt?: string,
|
|
106
100
|
hasTopLevelQuestWithoutSubs?: boolean,
|
|
107
101
|
): NudgeCandidate[] {
|
|
@@ -111,7 +105,7 @@ export class QuestUsageTracker {
|
|
|
111
105
|
if (this.totalToolCalls >= this.config.nudges.toolCallThreshold && !this.hasEverUsedQuestTool) {
|
|
112
106
|
candidates.push({
|
|
113
107
|
index: 0,
|
|
114
|
-
message: `QUEST REMINDER: You have made
|
|
108
|
+
message: `QUEST REMINDER: You have made multiple tool calls but have NEVER used the quest tool this session. USE the quest tool to initialize tracking and break your work into concrete steps. ${ACKNOWLEDGEMENT}`,
|
|
115
109
|
});
|
|
116
110
|
}
|
|
117
111
|
|
|
@@ -119,7 +113,7 @@ export class QuestUsageTracker {
|
|
|
119
113
|
if (activeQuestCount === 0 && latestPrompt && this.isComplexPrompt(latestPrompt)) {
|
|
120
114
|
candidates.push({
|
|
121
115
|
index: 1,
|
|
122
|
-
message: `QUEST REMINDER: Your latest prompt
|
|
116
|
+
message: `QUEST REMINDER: Your latest prompt looks like a complex task, but there are 0 active quests. USE the quest tool to break it into concrete, trackable steps. ${ACKNOWLEDGEMENT}`,
|
|
123
117
|
});
|
|
124
118
|
}
|
|
125
119
|
|
|
@@ -130,10 +124,9 @@ export class QuestUsageTracker {
|
|
|
130
124
|
this.consecutiveNonQuestToolCalls >= this.config.nudges.timeBasedToolCallThreshold &&
|
|
131
125
|
Date.now() - this.lastQuestToolTime >= this.config.nudges.hintIntervalMinutes * 60 * 1000
|
|
132
126
|
) {
|
|
133
|
-
const questContext = formatActiveQuests(allQuests);
|
|
134
127
|
candidates.push({
|
|
135
128
|
index: 2,
|
|
136
|
-
message: `QUEST REMINDER: It has been a while since your last quest tool use and
|
|
129
|
+
message: `QUEST REMINDER: It has been a while since your last quest tool use and many tools have been called since then. ALIGN on quest status before continuing. ${ACKNOWLEDGEMENT}`,
|
|
137
130
|
});
|
|
138
131
|
}
|
|
139
132
|
|
|
@@ -144,7 +137,7 @@ export class QuestUsageTracker {
|
|
|
144
137
|
) {
|
|
145
138
|
candidates.push({
|
|
146
139
|
index: 3,
|
|
147
|
-
message: `QUEST REMINDER: You have made
|
|
140
|
+
message: `QUEST REMINDER: You have made several consecutive tool calls without using the quest tool and there are 0 active quests. TRACK your work with specific, actionable quests. ${ACKNOWLEDGEMENT}`,
|
|
148
141
|
});
|
|
149
142
|
}
|
|
150
143
|
|
|
@@ -157,7 +150,7 @@ export class QuestUsageTracker {
|
|
|
157
150
|
) {
|
|
158
151
|
candidates.push({
|
|
159
152
|
index: 4,
|
|
160
|
-
message: `QUEST REMINDER: You have made
|
|
153
|
+
message: `QUEST REMINDER: You have made several consecutive tool calls without using the quest tool and have active top-level quests without steps. Consider whether decomposing them with the \`split\` action would help track progress. ${ACKNOWLEDGEMENT}`,
|
|
161
154
|
});
|
|
162
155
|
}
|
|
163
156
|
|
|
@@ -168,10 +161,9 @@ export class QuestUsageTracker {
|
|
|
168
161
|
this.lastQuestToolTime > 0 &&
|
|
169
162
|
Date.now() - this.lastQuestToolTime >= this.config.nudges.hintIntervalMinutes * 60 * 1000
|
|
170
163
|
) {
|
|
171
|
-
const questContext = formatActiveQuests(allQuests);
|
|
172
164
|
candidates.push({
|
|
173
165
|
index: 5,
|
|
174
|
-
message: `QUEST REMINDER: You have made
|
|
166
|
+
message: `QUEST REMINDER: You have made many consecutive tool calls without using the quest tool despite having active quests. UPDATE your quest progress to reflect current status. ${ACKNOWLEDGEMENT}`,
|
|
175
167
|
});
|
|
176
168
|
}
|
|
177
169
|
|
|
@@ -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,39 @@
|
|
|
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(
|
|
9
|
+
private readonly icon: string,
|
|
10
|
+
private readonly enabled: boolean = true,
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
update(
|
|
14
|
+
questLog: QuestLog,
|
|
15
|
+
ui: { setStatus(key: string, text: string | undefined): void },
|
|
16
|
+
theme: Theme,
|
|
17
|
+
): void {
|
|
18
|
+
if (!this.enabled) return;
|
|
19
|
+
|
|
20
|
+
const all = questLog.getAll();
|
|
21
|
+
const total = all.length;
|
|
22
|
+
const done = all.filter((q) => q.done).length;
|
|
23
|
+
|
|
24
|
+
const text = this.formatStatus(total, done, theme);
|
|
25
|
+
ui.setStatus(this.key, text);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private formatStatus(total: number, done: number, theme: Theme): string | undefined {
|
|
29
|
+
if (total === 0) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const filled = Math.round((done / total) * this.barWidth);
|
|
34
|
+
const empty = this.barWidth - filled;
|
|
35
|
+
const bar = theme.fg("success", "▰".repeat(filled)) + theme.fg("muted", "▱".repeat(empty));
|
|
36
|
+
|
|
37
|
+
return `${theme.fg("accent", this.icon)} ${bar} ${theme.fg("dim", `${done}/${total}`)}`;
|
|
38
|
+
}
|
|
39
|
+
}
|
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(
|