pi-quests 0.6.2 → 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 +11 -0
- package/README.md +1 -1
- package/package.json +18 -13
- package/src/commands/handler.ts +13 -9
- package/src/commands/parse-args.ts +26 -14
- package/src/config.ts +2 -62
- package/src/index.ts +4 -42
- package/src/prompts/skill.md +4 -2
- package/src/quest/dataplane.ts +442 -115
- package/src/quest/formatters.ts +16 -2
- package/src/quest/tracker.ts +26 -152
- package/src/quest/types.ts +4 -2
- 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 +31 -10
- package/src/tools/handler.ts +75 -65
- package/src/tools/params.ts +9 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
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
|
+
|
|
10
|
+
## [0.7.0] - 2026-04-26
|
|
11
|
+
|
|
12
|
+
- feat: add batch toggle and batch delete support for quests (#2) — @pascal-de-ladurantaye
|
|
13
|
+
- feat: add redo action and rename revert to undo
|
|
14
|
+
- chore: migrate from taskfile to npm scripts and update dependencies
|
|
15
|
+
|
|
5
16
|
## [0.6.2] - 2026-04-17
|
|
6
17
|
|
|
7
18
|
- fix: system prompt was too weak
|
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,25 +32,30 @@
|
|
|
32
32
|
"LICENSE.md"
|
|
33
33
|
],
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
38
|
-
"
|
|
35
|
+
"@earendil-works/pi-ai": "*",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
37
|
+
"@earendil-works/pi-tui": "*",
|
|
38
|
+
"typebox": "*"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@biomejs/biome": "^2.4.
|
|
42
|
-
"@
|
|
43
|
-
"@
|
|
44
|
-
"
|
|
45
|
-
"
|
|
41
|
+
"@biomejs/biome": "^2.4.13",
|
|
42
|
+
"@earendil-works/pi-coding-agent": "^0.75.5",
|
|
43
|
+
"@earendil-works/pi-tui": "^0.75.5",
|
|
44
|
+
"typebox": "^1.1.33",
|
|
45
|
+
"typescript": "^6.0.3",
|
|
46
|
+
"vitest": "^4.1.5"
|
|
46
47
|
},
|
|
47
48
|
"scripts": {
|
|
48
49
|
"test": "vitest run",
|
|
49
50
|
"test:watch": "vitest",
|
|
50
51
|
"typecheck": "tsc --noEmit",
|
|
51
|
-
"style": "biome check ./src ./test",
|
|
52
|
-
"style:fix": "biome check --write --unsafe --no-errors-on-unmatched ./src ./test",
|
|
52
|
+
"style": "biome check --error-on-warnings ./src ./test",
|
|
53
|
+
"style:fix": "biome check --write --unsafe --no-errors-on-unmatched --error-on-warnings ./src ./test",
|
|
53
54
|
"format": "biome format --write --no-errors-on-unmatched ./src ./test",
|
|
54
|
-
"prepublishOnly": "npx tsc --noEmit && npx vitest run"
|
|
55
|
+
"prepublishOnly": "npx tsc --noEmit && npx vitest run",
|
|
56
|
+
"prepare": "bash scripts/setup.sh",
|
|
57
|
+
"cleanup": "bash scripts/cleanup.sh",
|
|
58
|
+
"dev": "bash scripts/dev.sh",
|
|
59
|
+
"logs": "bash scripts/logs.sh"
|
|
55
60
|
}
|
|
56
61
|
}
|
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";
|
|
@@ -25,7 +25,8 @@ type MutatingCommand = Extract<
|
|
|
25
25
|
| typeof QUEST_ACTIONS.clear
|
|
26
26
|
| typeof QUEST_ACTIONS.reorder
|
|
27
27
|
| typeof QUEST_ACTIONS.reparent
|
|
28
|
-
| typeof QUEST_ACTIONS.
|
|
28
|
+
| typeof QUEST_ACTIONS.undo
|
|
29
|
+
| typeof QUEST_ACTIONS.redo;
|
|
29
30
|
}
|
|
30
31
|
>["action"];
|
|
31
32
|
|
|
@@ -41,13 +42,13 @@ const commandActionBuilders: {
|
|
|
41
42
|
id: p.id,
|
|
42
43
|
descriptions: p.descriptions,
|
|
43
44
|
}),
|
|
44
|
-
[QUEST_ACTIONS.toggle]: (p) => ({ type: QUEST_ACTIONS.toggle,
|
|
45
|
+
[QUEST_ACTIONS.toggle]: (p) => ({ type: QUEST_ACTIONS.toggle, ids: p.ids }),
|
|
45
46
|
[QUEST_ACTIONS.update]: (p) => ({
|
|
46
47
|
type: QUEST_ACTIONS.update,
|
|
47
48
|
id: p.id,
|
|
48
49
|
description: p.description,
|
|
49
50
|
}),
|
|
50
|
-
[QUEST_ACTIONS.delete]: (p) => ({ type: QUEST_ACTIONS.delete,
|
|
51
|
+
[QUEST_ACTIONS.delete]: (p) => ({ type: QUEST_ACTIONS.delete, ids: p.ids }),
|
|
51
52
|
[QUEST_ACTIONS.clear]: (p) => ({ type: QUEST_ACTIONS.clear, all: p.all }),
|
|
52
53
|
[QUEST_ACTIONS.reorder]: (p) => ({
|
|
53
54
|
type: QUEST_ACTIONS.reorder,
|
|
@@ -59,7 +60,8 @@ const commandActionBuilders: {
|
|
|
59
60
|
id: p.id,
|
|
60
61
|
parentId: p.parentId,
|
|
61
62
|
}),
|
|
62
|
-
[QUEST_ACTIONS.
|
|
63
|
+
[QUEST_ACTIONS.undo]: () => ({ type: QUEST_ACTIONS.undo }),
|
|
64
|
+
[QUEST_ACTIONS.redo]: () => ({ type: QUEST_ACTIONS.redo }),
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
export function openQuestList(
|
|
@@ -143,7 +145,8 @@ export function createQuestsHandler(
|
|
|
143
145
|
case QUEST_ACTIONS.clear:
|
|
144
146
|
case QUEST_ACTIONS.reorder:
|
|
145
147
|
case QUEST_ACTIONS.reparent:
|
|
146
|
-
case QUEST_ACTIONS.
|
|
148
|
+
case QUEST_ACTIONS.undo:
|
|
149
|
+
case QUEST_ACTIONS.redo: {
|
|
147
150
|
const builder = commandActionBuilders[parsed.action];
|
|
148
151
|
const action = builder(parsed as never);
|
|
149
152
|
const result = questLog.execute(action);
|
|
@@ -161,12 +164,13 @@ export function createQuestsHandler(
|
|
|
161
164
|
lines.push(" add <description> - Add a new top-level quest");
|
|
162
165
|
lines.push(" add-step <id> <description> - Split a quest into a step");
|
|
163
166
|
lines.push(" list - List all quests");
|
|
164
|
-
lines.push(" toggle <id>
|
|
165
|
-
lines.push(" delete <id>
|
|
167
|
+
lines.push(" toggle <id> [moreIds...] - Toggle one or more quests");
|
|
168
|
+
lines.push(" delete <id> [moreIds...] - Delete one or more quests");
|
|
166
169
|
lines.push(" update <id> <desc> - Update a quest description");
|
|
167
170
|
lines.push(" reparent <id> [parentId] - Reparent a quest/step (omit parentId to promote)");
|
|
168
171
|
lines.push(" reorder <id> <targetId> - Reorder a quest before the target quest");
|
|
169
|
-
lines.push("
|
|
172
|
+
lines.push(" undo - Undo the last quest change");
|
|
173
|
+
lines.push(" redo - Redo the last undone action");
|
|
170
174
|
lines.push(" clear [all] - Clear completed quests, or optionally all quests");
|
|
171
175
|
lines.push(" version - Show version");
|
|
172
176
|
lines.push(" changelog - Show changelog");
|
|
@@ -5,12 +5,13 @@ export type ParsedArgs =
|
|
|
5
5
|
| { action: typeof QUEST_ACTIONS.add; descriptions: string[] }
|
|
6
6
|
| { action: typeof QUEST_ACTIONS.split; id: string; descriptions: string[] }
|
|
7
7
|
| { action: typeof QUEST_ACTIONS.list }
|
|
8
|
-
| { action: typeof QUEST_ACTIONS.toggle;
|
|
8
|
+
| { action: typeof QUEST_ACTIONS.toggle; ids: string[] }
|
|
9
9
|
| { action: typeof QUEST_ACTIONS.update; id: string; description: string }
|
|
10
|
-
| { action: typeof QUEST_ACTIONS.delete;
|
|
10
|
+
| { action: typeof QUEST_ACTIONS.delete; ids: string[] }
|
|
11
11
|
| { action: typeof QUEST_ACTIONS.clear; all?: boolean }
|
|
12
12
|
| { action: typeof QUEST_ACTIONS.reorder; id: string; targetId: string }
|
|
13
|
-
| { action: typeof QUEST_ACTIONS.
|
|
13
|
+
| { action: typeof QUEST_ACTIONS.undo }
|
|
14
|
+
| { action: typeof QUEST_ACTIONS.redo }
|
|
14
15
|
| { action: typeof QUEST_ACTIONS.reparent; id: string; parentId?: string }
|
|
15
16
|
| { action: "help" }
|
|
16
17
|
| { action: "version" }
|
|
@@ -40,13 +41,14 @@ export function parseQuestArgs(args: string, idLength = 2): ParsedArgs {
|
|
|
40
41
|
// Quest actions without arguments
|
|
41
42
|
if (command === QUEST_ACTIONS.list) return { action: QUEST_ACTIONS.list };
|
|
42
43
|
if (command === QUEST_ACTIONS.clear) return parseClearArgs(rest);
|
|
43
|
-
if (command === QUEST_ACTIONS.
|
|
44
|
+
if (command === QUEST_ACTIONS.undo) return { action: QUEST_ACTIONS.undo };
|
|
45
|
+
if (command === QUEST_ACTIONS.redo) return { action: QUEST_ACTIONS.redo };
|
|
44
46
|
|
|
45
47
|
// Quest actions with arguments
|
|
46
48
|
if (command === QUEST_ACTIONS.add) return parseAddArgs(rest);
|
|
47
49
|
if (command === "add-step") return parseAddStepArgs(rest, idLength);
|
|
48
|
-
if (command === QUEST_ACTIONS.toggle) return
|
|
49
|
-
if (command === QUEST_ACTIONS.delete) return
|
|
50
|
+
if (command === QUEST_ACTIONS.toggle) return parseToggleArgs(rest, idLength);
|
|
51
|
+
if (command === QUEST_ACTIONS.delete) return parseDeleteArgs(rest, idLength);
|
|
50
52
|
if (command === QUEST_ACTIONS.update) return parseUpdateArgs(rest, idLength);
|
|
51
53
|
if (command === QUEST_ACTIONS.reorder) return parseReorderArgs(rest, idLength);
|
|
52
54
|
if (command === QUEST_ACTIONS.reparent) return parseReparentArgs(rest, idLength);
|
|
@@ -70,16 +72,26 @@ function parseAddStepArgs(tokens: string[], idLength: number): ParsedArgs {
|
|
|
70
72
|
return { action: QUEST_ACTIONS.split, id, descriptions: [description] };
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
function
|
|
74
|
-
action: typeof QUEST_ACTIONS.toggle | typeof QUEST_ACTIONS.delete,
|
|
75
|
-
tokens: string[],
|
|
76
|
-
idLength: number,
|
|
77
|
-
): ParsedArgs {
|
|
78
|
-
const id = tokens[0] ? tokens[0].toLowerCase() : "";
|
|
75
|
+
function parseToggleArgs(tokens: string[], idLength: number): ParsedArgs {
|
|
79
76
|
const pattern = new RegExp(`^[0-9a-f]{${idLength}}$`);
|
|
80
|
-
|
|
77
|
+
const ids = tokens.map((token) => token.toLowerCase());
|
|
78
|
+
|
|
79
|
+
if (ids.length === 0 || ids.some((id) => !pattern.test(id))) {
|
|
80
|
+
return { error: "Usage: /quests toggle <id> [moreIds...]" };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { action: QUEST_ACTIONS.toggle, ids };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseDeleteArgs(tokens: string[], idLength: number): ParsedArgs {
|
|
87
|
+
const pattern = new RegExp(`^[0-9a-f]{${idLength}}$`);
|
|
88
|
+
const ids = tokens.map((token) => token.toLowerCase());
|
|
89
|
+
|
|
90
|
+
if (ids.length === 0 || ids.some((id) => !pattern.test(id))) {
|
|
91
|
+
return { error: "Usage: /quests delete <id> [moreIds...]" };
|
|
92
|
+
}
|
|
81
93
|
|
|
82
|
-
return { action,
|
|
94
|
+
return { action: QUEST_ACTIONS.delete, ids };
|
|
83
95
|
}
|
|
84
96
|
|
|
85
97
|
function parseUpdateArgs(tokens: string[], idLength: number): ParsedArgs {
|
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/prompts/skill.md
CHANGED
|
@@ -20,7 +20,8 @@ This skill provides guidelines and best practices for using the quest management
|
|
|
20
20
|
| `clear` | — | Remove completed quests. `all: true` removes everything. |
|
|
21
21
|
| `reorder` | `id`, `targetId` | Move a top-level quest before `targetId`. Steps cannot be reordered. |
|
|
22
22
|
| `reparent` | `id`, `parentId?` | Demote to step under `parentId`, or promote to top-level if `parentId` omitted. |
|
|
23
|
-
| `
|
|
23
|
+
| `undo` | — | Undo the most recent mutating action. One level only. |
|
|
24
|
+
| `redo` | — | Redo the most recently undone action. |
|
|
24
25
|
| `rules` / `skill` | — | Return this document. |
|
|
25
26
|
|
|
26
27
|
## Workflows
|
|
@@ -51,7 +52,8 @@ This skill provides guidelines and best practices for using the quest management
|
|
|
51
52
|
- NEVER try to nest steps. Only top-level quests can be parents.
|
|
52
53
|
- NEVER mark a parent as done if they have incomplete steps.
|
|
53
54
|
- NEVER delete a quest with incomplete steps.
|
|
54
|
-
- Use
|
|
55
|
+
- Use undo if you make a mistake to undo an action.
|
|
56
|
+
- Use redo to replay an action you just undid. Redo is cleared when a new mutation happens.
|
|
55
57
|
- ALWAYS use the hex ID, never the positional number when referring to quests in actions.
|
|
56
58
|
- ALWAYS Use `toggle` for done states. NEVER use `update` to append "DONE" or any completion marker to a description.
|
|
57
59
|
- If a parent toggle is blocked, check that all its steps are done first.
|