pi-quests 0.7.0 → 0.8.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 +6 -6
- package/src/commands/handler.ts +1 -1
- package/src/config.ts +2 -62
- package/src/index.ts +4 -42
- package/src/quest/dataplane.ts +1 -1
- package/src/quest/tracker.ts +26 -152
- package/src/renderers/changelog.ts +4 -4
- package/src/renderers/commands.ts +2 -2
- package/src/renderers/quests.ts +2 -2
- package/src/renderers/status.ts +1 -1
- package/src/renderers/tools.ts +2 -2
- package/src/tools/handler.ts +1 -1
- package/src/tools/params.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.8.0] - 2026-05-26
|
|
6
|
+
|
|
7
|
+
- feat: migrate to `@earendil-works`
|
|
8
|
+
- feat: remove the nudge-based acknowledgement system
|
|
9
|
+
|
|
5
10
|
## [0.7.0] - 2026-04-26
|
|
6
11
|
|
|
7
12
|
- feat: add batch toggle and batch delete support for quests (#2) — @pascal-de-ladurantaye
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-quests",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "A quest-log for your pi. Keep your agent on track, one quest at a time.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -32,15 +32,15 @@
|
|
|
32
32
|
"LICENSE.md"
|
|
33
33
|
],
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
35
|
+
"@earendil-works/pi-ai": "*",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
37
|
+
"@earendil-works/pi-tui": "*",
|
|
38
38
|
"typebox": "*"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@biomejs/biome": "^2.4.13",
|
|
42
|
-
"@
|
|
43
|
-
"@
|
|
42
|
+
"@earendil-works/pi-coding-agent": "^0.75.5",
|
|
43
|
+
"@earendil-works/pi-tui": "^0.75.5",
|
|
44
44
|
"typebox": "^1.1.33",
|
|
45
45
|
"typescript": "^6.0.3",
|
|
46
46
|
"vitest": "^4.1.5"
|
package/src/commands/handler.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
ExtensionAPI,
|
|
4
4
|
ExtensionCommandContext,
|
|
5
5
|
ExtensionContext,
|
|
6
|
-
} from "@
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import type { ResolvedConfig } from "../config.js";
|
|
8
8
|
import { logger } from "../logger.js";
|
|
9
9
|
import type { QuestAction, QuestLog } from "../quest/dataplane.js";
|
package/src/config.ts
CHANGED
|
@@ -1,53 +1,17 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import type { ExtensionContext } from "@
|
|
4
|
-
import { getAgentDir } from "@
|
|
3
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
|
|
6
6
|
export interface ResolvedConfig {
|
|
7
7
|
ids: { length: number };
|
|
8
8
|
display: { pageSize: number; progressBarMaxWidth: number; icon: string; showStatus: boolean };
|
|
9
|
-
nudges: {
|
|
10
|
-
enable: boolean;
|
|
11
|
-
toolCallThreshold: number;
|
|
12
|
-
hintIntervalMinutes: number;
|
|
13
|
-
timeBasedToolCallThreshold: number;
|
|
14
|
-
zeroActiveToolCallThreshold: number;
|
|
15
|
-
staleProgressToolCallThreshold: number;
|
|
16
|
-
stepSuggestionToolCallThreshold: number;
|
|
17
|
-
complexTaskKeywords: string[];
|
|
18
|
-
};
|
|
19
|
-
validation: { fakeDonePattern: string };
|
|
20
9
|
shortcuts?: { openQuests?: string };
|
|
21
10
|
}
|
|
22
11
|
|
|
23
|
-
export const DEFAULT_COMPLEX_TASK_KEYWORDS = [
|
|
24
|
-
"implement",
|
|
25
|
-
"refactor",
|
|
26
|
-
"investigate",
|
|
27
|
-
"analyze",
|
|
28
|
-
"audit",
|
|
29
|
-
"design",
|
|
30
|
-
"fix",
|
|
31
|
-
"plan",
|
|
32
|
-
"build",
|
|
33
|
-
] as const;
|
|
34
|
-
|
|
35
|
-
export const DEFAULT_FAKE_DONE_PATTERN = String.raw`\s[-\u2013\u2014]\s*(DONE|COMPLETED|FINISHED)$|\s[([](DONE|COMPLETED|FINISHED)[)\]]$`;
|
|
36
|
-
|
|
37
12
|
export const DEFAULT_CONFIG: ResolvedConfig = {
|
|
38
13
|
ids: { length: 2 },
|
|
39
14
|
display: { pageSize: 10, progressBarMaxWidth: 24, icon: "", showStatus: true },
|
|
40
|
-
nudges: {
|
|
41
|
-
enable: true,
|
|
42
|
-
toolCallThreshold: 8,
|
|
43
|
-
hintIntervalMinutes: 4,
|
|
44
|
-
timeBasedToolCallThreshold: 8,
|
|
45
|
-
zeroActiveToolCallThreshold: 8,
|
|
46
|
-
staleProgressToolCallThreshold: 12,
|
|
47
|
-
stepSuggestionToolCallThreshold: 10,
|
|
48
|
-
complexTaskKeywords: [...DEFAULT_COMPLEX_TASK_KEYWORDS],
|
|
49
|
-
},
|
|
50
|
-
validation: { fakeDonePattern: DEFAULT_FAKE_DONE_PATTERN },
|
|
51
15
|
shortcuts: {},
|
|
52
16
|
};
|
|
53
17
|
|
|
@@ -119,30 +83,6 @@ export function getConfig(ctx: Pick<ExtensionContext, "cwd">): ResolvedConfig {
|
|
|
119
83
|
icon: user.display?.icon ?? DEFAULT_CONFIG.display.icon,
|
|
120
84
|
showStatus: user.display?.showStatus ?? DEFAULT_CONFIG.display.showStatus,
|
|
121
85
|
},
|
|
122
|
-
nudges: {
|
|
123
|
-
enable: user.nudges?.enable ?? DEFAULT_CONFIG.nudges.enable,
|
|
124
|
-
toolCallThreshold: user.nudges?.toolCallThreshold ?? DEFAULT_CONFIG.nudges.toolCallThreshold,
|
|
125
|
-
hintIntervalMinutes:
|
|
126
|
-
user.nudges?.hintIntervalMinutes ?? DEFAULT_CONFIG.nudges.hintIntervalMinutes,
|
|
127
|
-
timeBasedToolCallThreshold:
|
|
128
|
-
user.nudges?.timeBasedToolCallThreshold ?? DEFAULT_CONFIG.nudges.timeBasedToolCallThreshold,
|
|
129
|
-
zeroActiveToolCallThreshold:
|
|
130
|
-
user.nudges?.zeroActiveToolCallThreshold ??
|
|
131
|
-
DEFAULT_CONFIG.nudges.zeroActiveToolCallThreshold,
|
|
132
|
-
staleProgressToolCallThreshold:
|
|
133
|
-
user.nudges?.staleProgressToolCallThreshold ??
|
|
134
|
-
DEFAULT_CONFIG.nudges.staleProgressToolCallThreshold,
|
|
135
|
-
stepSuggestionToolCallThreshold:
|
|
136
|
-
user.nudges?.stepSuggestionToolCallThreshold ??
|
|
137
|
-
DEFAULT_CONFIG.nudges.stepSuggestionToolCallThreshold,
|
|
138
|
-
complexTaskKeywords: [
|
|
139
|
-
...(user.nudges?.complexTaskKeywords ?? DEFAULT_CONFIG.nudges.complexTaskKeywords),
|
|
140
|
-
],
|
|
141
|
-
},
|
|
142
|
-
validation: {
|
|
143
|
-
fakeDonePattern:
|
|
144
|
-
user.validation?.fakeDonePattern ?? DEFAULT_CONFIG.validation.fakeDonePattern,
|
|
145
|
-
},
|
|
146
86
|
shortcuts: {
|
|
147
87
|
openQuests: user.shortcuts?.openQuests,
|
|
148
88
|
},
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import type { KeyId } from "@mariozechner/pi-tui";
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { KeyId } from "@earendil-works/pi-tui";
|
|
4
3
|
import { createQuestsHandler, openQuestList } from "./commands/handler.js";
|
|
5
4
|
import { DEFAULT_CONFIG, getConfig, type ResolvedConfig } from "./config.js";
|
|
6
5
|
import { logger } from "./logger.js";
|
|
@@ -24,12 +23,9 @@ import { registerQuestTool } from "./tools/handler.js";
|
|
|
24
23
|
const NO_QUESTS_REMINDER =
|
|
25
24
|
"No active quests. Use the learn_quests tool to understand how to use quests and use quests to track this task. DO THIS NOW and acknowledge it.";
|
|
26
25
|
|
|
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
|
-
|
|
30
26
|
export default function (pi: ExtensionAPI): void {
|
|
31
27
|
let questLog = new QuestLog();
|
|
32
|
-
let tracker = new QuestUsageTracker(
|
|
28
|
+
let tracker = new QuestUsageTracker();
|
|
33
29
|
let config: ResolvedConfig = DEFAULT_CONFIG;
|
|
34
30
|
let statusWidget = new QuestStatusWidget(
|
|
35
31
|
DEFAULT_CONFIG.display.icon,
|
|
@@ -56,7 +52,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
56
52
|
pi.on("session_start", async (_event, ctx) => {
|
|
57
53
|
config = getConfig(ctx);
|
|
58
54
|
questLog = new QuestLog(config);
|
|
59
|
-
tracker = new QuestUsageTracker(
|
|
55
|
+
tracker = new QuestUsageTracker();
|
|
60
56
|
statusWidget = new QuestStatusWidget(config.display.icon, config.display.showStatus);
|
|
61
57
|
questLog.reconstructFromSession(ctx);
|
|
62
58
|
if (questLog.getAll().length > 0) tracker.markQuestToolUsed();
|
|
@@ -79,8 +75,6 @@ export default function (pi: ExtensionAPI): void {
|
|
|
79
75
|
statusWidget.update(questLog, ctx.ui, ctx.ui.theme);
|
|
80
76
|
});
|
|
81
77
|
|
|
82
|
-
pi.on("turn_start", async () => tracker.clearTurnNudge());
|
|
83
|
-
|
|
84
78
|
pi.on("tool_execution_end", async (event, ctx) => {
|
|
85
79
|
tracker.onToolExecution(event.toolName);
|
|
86
80
|
if (event.toolName === "quest") {
|
|
@@ -88,38 +82,6 @@ export default function (pi: ExtensionAPI): void {
|
|
|
88
82
|
}
|
|
89
83
|
});
|
|
90
84
|
|
|
91
|
-
pi.on("context", async (event) => {
|
|
92
|
-
if (!config.nudges.enable) return undefined;
|
|
93
|
-
const latestPrompt = event.messages
|
|
94
|
-
.filter((m) => m.role === "user")
|
|
95
|
-
.map((m) => (typeof m.content === "string" ? m.content : ""))
|
|
96
|
-
.join("\n");
|
|
97
|
-
|
|
98
|
-
const allQuests = questLog.getAll();
|
|
99
|
-
const activeQuests = allQuests.filter((q) => !q.done);
|
|
100
|
-
const activeQuestCount = activeQuests.length;
|
|
101
|
-
const activeTopLevel = activeQuests.filter((q) => !(q as { parentId?: string }).parentId);
|
|
102
|
-
const hasTopLevelQuestWithoutSubs = activeTopLevel.some(
|
|
103
|
-
(q) => !allQuests.some((step) => (step as { parentId?: string }).parentId === q.id),
|
|
104
|
-
);
|
|
105
|
-
const nudge = tracker.getNudge(activeQuestCount, latestPrompt, hasTopLevelQuestWithoutSubs);
|
|
106
|
-
|
|
107
|
-
const fakeDoneRegex = new RegExp(config.validation.fakeDonePattern, "i");
|
|
108
|
-
const fakeDone = questLog.getAll().find((q) => !q.done && fakeDoneRegex.test(q.description));
|
|
109
|
-
if (!nudge && !fakeDone) return undefined;
|
|
110
|
-
|
|
111
|
-
const parts: string[] = [];
|
|
112
|
-
if (nudge) parts.push(nudge);
|
|
113
|
-
if (fakeDone) parts.push(FAKE_DONE_REMINDER);
|
|
114
|
-
|
|
115
|
-
const reminder: UserMessage = {
|
|
116
|
-
role: "user",
|
|
117
|
-
content: parts.join("\n"),
|
|
118
|
-
timestamp: Date.now(),
|
|
119
|
-
};
|
|
120
|
-
return { messages: [...event.messages, reminder] };
|
|
121
|
-
});
|
|
122
|
-
|
|
123
85
|
pi.on("before_agent_start", async (event) => {
|
|
124
86
|
if (questLog.getAll().length === 0)
|
|
125
87
|
return {
|
package/src/quest/dataplane.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentToolResult, ExtensionContext } from "@
|
|
1
|
+
import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { DEFAULT_CONFIG, type ResolvedConfig } from "../config.js";
|
|
3
3
|
import { logger } from "../logger.js";
|
|
4
4
|
import { getQuestSkillDocument } from "../prompts.js";
|
package/src/quest/tracker.ts
CHANGED
|
@@ -1,34 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
export interface QuestUsageStats {
|
|
2
|
+
readonly totalToolCalls: number;
|
|
3
|
+
readonly questToolCalls: number;
|
|
4
|
+
readonly nonQuestToolCalls: number;
|
|
5
|
+
readonly consecutiveNonQuestToolCalls: number;
|
|
6
|
+
readonly hasUsedQuestTool: boolean;
|
|
7
|
+
readonly lastQuestToolTimestamp: number | undefined;
|
|
8
|
+
}
|
|
7
9
|
|
|
8
10
|
export class QuestUsageTracker {
|
|
9
11
|
private totalToolCalls = 0;
|
|
12
|
+
private questToolCalls = 0;
|
|
13
|
+
private nonQuestToolCalls = 0;
|
|
10
14
|
private consecutiveNonQuestToolCalls = 0;
|
|
11
15
|
private hasEverUsedQuestTool = false;
|
|
12
|
-
private
|
|
13
|
-
private nudgedThisTurn = false;
|
|
14
|
-
private lastNudgeTime = 0;
|
|
15
|
-
private lastNudgeIndex = -1;
|
|
16
|
-
|
|
17
|
-
constructor(private readonly config: ResolvedConfig) {}
|
|
16
|
+
private lastQuestToolTimestamp: number | undefined;
|
|
18
17
|
|
|
19
18
|
onToolExecution(toolName: string): void {
|
|
20
19
|
this.totalToolCalls++;
|
|
21
20
|
if (toolName === "quest") {
|
|
21
|
+
this.questToolCalls++;
|
|
22
22
|
this.hasEverUsedQuestTool = true;
|
|
23
|
-
this.
|
|
23
|
+
this.lastQuestToolTimestamp = Date.now();
|
|
24
24
|
this.consecutiveNonQuestToolCalls = 0;
|
|
25
|
-
|
|
26
|
-
this.consecutiveNonQuestToolCalls++;
|
|
25
|
+
return;
|
|
27
26
|
}
|
|
28
|
-
}
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
this.
|
|
28
|
+
this.nonQuestToolCalls++;
|
|
29
|
+
this.consecutiveNonQuestToolCalls++;
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
get hasUsedQuestTool(): boolean {
|
|
@@ -37,141 +35,17 @@ export class QuestUsageTracker {
|
|
|
37
35
|
|
|
38
36
|
markQuestToolUsed(): void {
|
|
39
37
|
this.hasEverUsedQuestTool = true;
|
|
40
|
-
this.
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
getNudge(
|
|
44
|
-
activeQuestCount: number,
|
|
45
|
-
latestPrompt?: string,
|
|
46
|
-
hasTopLevelQuestWithoutSubs?: boolean,
|
|
47
|
-
): string | undefined {
|
|
48
|
-
if (this.nudgedThisTurn) {
|
|
49
|
-
logger.debug("quests:tracker", "nudge-suppressed", { reason: "turn-limit" });
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const now = Date.now();
|
|
54
|
-
const cooldownMs = this.config.nudges.hintIntervalMinutes * 60 * 1000;
|
|
55
|
-
if (this.lastNudgeTime > 0 && now - this.lastNudgeTime < cooldownMs) {
|
|
56
|
-
logger.debug("quests:tracker", "nudge-suppressed", {
|
|
57
|
-
reason: "cooldown",
|
|
58
|
-
elapsed: now - this.lastNudgeTime,
|
|
59
|
-
cooldownMs,
|
|
60
|
-
});
|
|
61
|
-
return undefined;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const eligible = this.getEligibleNudges(
|
|
65
|
-
activeQuestCount,
|
|
66
|
-
latestPrompt,
|
|
67
|
-
hasTopLevelQuestWithoutSubs,
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
logger.debug("quests:tracker", "nudge-candidates", {
|
|
71
|
-
eligible: eligible.length,
|
|
72
|
-
indices: eligible.map((c) => c.index),
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Rotate priority: start checking from the nudge after the last one that fired
|
|
76
|
-
const rotated = [
|
|
77
|
-
...eligible.filter((n) => n.index > this.lastNudgeIndex),
|
|
78
|
-
...eligible.filter((n) => n.index <= this.lastNudgeIndex),
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
const winner = rotated[0];
|
|
82
|
-
if (winner) {
|
|
83
|
-
this.nudgedThisTurn = true;
|
|
84
|
-
this.lastNudgeTime = now;
|
|
85
|
-
|
|
86
|
-
logger.debug("quests:tracker", "nudge-fired", {
|
|
87
|
-
winner: winner.index,
|
|
88
|
-
rotatedFrom: this.lastNudgeIndex,
|
|
89
|
-
});
|
|
90
|
-
this.lastNudgeIndex = winner.index;
|
|
91
|
-
return winner.message;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private getEligibleNudges(
|
|
98
|
-
activeQuestCount: number,
|
|
99
|
-
latestPrompt?: string,
|
|
100
|
-
hasTopLevelQuestWithoutSubs?: boolean,
|
|
101
|
-
): NudgeCandidate[] {
|
|
102
|
-
const candidates: NudgeCandidate[] = [];
|
|
103
|
-
|
|
104
|
-
// 0. Initialization nudge
|
|
105
|
-
if (this.totalToolCalls >= this.config.nudges.toolCallThreshold && !this.hasEverUsedQuestTool) {
|
|
106
|
-
candidates.push({
|
|
107
|
-
index: 0,
|
|
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}`,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 1. Complex-task entrypoint nudge
|
|
113
|
-
if (activeQuestCount === 0 && latestPrompt && this.isComplexPrompt(latestPrompt)) {
|
|
114
|
-
candidates.push({
|
|
115
|
-
index: 1,
|
|
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}`,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// 2. Time-based alignment nudge
|
|
121
|
-
if (
|
|
122
|
-
this.hasEverUsedQuestTool &&
|
|
123
|
-
this.lastQuestToolTime > 0 &&
|
|
124
|
-
this.consecutiveNonQuestToolCalls >= this.config.nudges.timeBasedToolCallThreshold &&
|
|
125
|
-
Date.now() - this.lastQuestToolTime >= this.config.nudges.hintIntervalMinutes * 60 * 1000
|
|
126
|
-
) {
|
|
127
|
-
candidates.push({
|
|
128
|
-
index: 2,
|
|
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}`,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 3. Zero-active sustained-work nudge
|
|
134
|
-
if (
|
|
135
|
-
this.consecutiveNonQuestToolCalls >= this.config.nudges.zeroActiveToolCallThreshold &&
|
|
136
|
-
activeQuestCount === 0
|
|
137
|
-
) {
|
|
138
|
-
candidates.push({
|
|
139
|
-
index: 3,
|
|
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}`,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// 4. Sub-quest suggestion nudge
|
|
145
|
-
if (
|
|
146
|
-
this.hasEverUsedQuestTool &&
|
|
147
|
-
this.consecutiveNonQuestToolCalls >= this.config.nudges.stepSuggestionToolCallThreshold &&
|
|
148
|
-
activeQuestCount > 0 &&
|
|
149
|
-
hasTopLevelQuestWithoutSubs
|
|
150
|
-
) {
|
|
151
|
-
candidates.push({
|
|
152
|
-
index: 4,
|
|
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}`,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// 5. Stale-progress sustained-work nudge (with time-gate)
|
|
158
|
-
if (
|
|
159
|
-
this.consecutiveNonQuestToolCalls >= this.config.nudges.staleProgressToolCallThreshold &&
|
|
160
|
-
activeQuestCount > 0 &&
|
|
161
|
-
this.lastQuestToolTime > 0 &&
|
|
162
|
-
Date.now() - this.lastQuestToolTime >= this.config.nudges.hintIntervalMinutes * 60 * 1000
|
|
163
|
-
) {
|
|
164
|
-
candidates.push({
|
|
165
|
-
index: 5,
|
|
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}`,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return candidates;
|
|
38
|
+
this.lastQuestToolTimestamp = Date.now();
|
|
171
39
|
}
|
|
172
40
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
41
|
+
getStats(): QuestUsageStats {
|
|
42
|
+
return {
|
|
43
|
+
totalToolCalls: this.totalToolCalls,
|
|
44
|
+
questToolCalls: this.questToolCalls,
|
|
45
|
+
nonQuestToolCalls: this.nonQuestToolCalls,
|
|
46
|
+
consecutiveNonQuestToolCalls: this.consecutiveNonQuestToolCalls,
|
|
47
|
+
hasUsedQuestTool: this.hasEverUsedQuestTool,
|
|
48
|
+
lastQuestToolTimestamp: this.lastQuestToolTimestamp,
|
|
49
|
+
};
|
|
176
50
|
}
|
|
177
51
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { MessageRenderer, MessageRenderOptions, Theme } from "@
|
|
2
|
-
import { getMarkdownTheme } from "@
|
|
3
|
-
import type { Component } from "@
|
|
4
|
-
import { Markdown } from "@
|
|
1
|
+
import type { MessageRenderer, MessageRenderOptions, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { Component } from "@earendil-works/pi-tui";
|
|
4
|
+
import { Markdown } from "@earendil-works/pi-tui";
|
|
5
5
|
import { logger } from "../logger.js";
|
|
6
6
|
|
|
7
7
|
export const questChangelogRenderer: MessageRenderer<{ content: string }> = (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Theme } from "@
|
|
2
|
-
import { Key, matchesKey, truncateToWidth, visibleWidth } from "@
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { ResolvedConfig } from "../config.js";
|
|
4
4
|
import { logger } from "../logger.js";
|
|
5
5
|
import type { QuestLog } from "../quest/dataplane.js";
|
package/src/renderers/quests.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Theme } from "@
|
|
2
|
-
import { visibleWidth } from "@
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { Quest, Step } from "../quest/types.js";
|
|
4
4
|
|
|
5
5
|
export function formatQuestRow(
|
package/src/renderers/status.ts
CHANGED
package/src/renderers/tools.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AgentToolResult, Theme } from "@
|
|
2
|
-
import { Text } from "@
|
|
1
|
+
import type { AgentToolResult, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { ResolvedConfig } from "../config.js";
|
|
4
4
|
import { logger } from "../logger.js";
|
|
5
5
|
import type { Quest } from "../quest/types.js";
|
package/src/tools/handler.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentToolResult, ExtensionAPI, Theme } from "@
|
|
1
|
+
import type { AgentToolResult, ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { type Static, Type } from "typebox";
|
|
3
3
|
import type { ResolvedConfig } from "../config.js";
|
|
4
4
|
import { logger } from "../logger.js";
|
package/src/tools/params.ts
CHANGED