pi-quests 0.1.0 → 0.3.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 +18 -0
- package/README.md +29 -12
- package/package.json +1 -1
- package/src/commands/changelog.ts +9 -0
- package/src/commands/handler.ts +142 -0
- package/src/commands/parse-args.ts +94 -0
- package/src/index.ts +57 -3
- package/src/logger.ts +11 -2
- package/src/prompts.ts +16 -0
- package/src/quest/dataplane.ts +430 -0
- package/src/quest/formatters.ts +35 -0
- package/src/quest/tracker.ts +87 -0
- package/src/quest/types.ts +35 -0
- package/src/renderers/commands.ts +13 -4
- package/src/renderers/tools.ts +19 -19
- package/src/tools/handler.ts +101 -0
- package/src/tools/params.ts +35 -0
- package/src/version.ts +9 -2
- package/src/commands/quests.ts +0 -220
- package/src/quests.ts +0 -222
- package/src/tools/quest.ts +0 -136
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.3.0] - 2026-04-11
|
|
6
|
+
|
|
7
|
+
- feat: add reorder action to move quests by position (dataplane, tool, /quests command, renderer, revert support)
|
|
8
|
+
- feat: extend clear action with optional all flag to remove all quests regardless of done state
|
|
9
|
+
- feat: detect appended completion markers (e.g. "- DONE") and inject correction nudge via context hook
|
|
10
|
+
- feat: update list rendering to show 1-based positions instead of raw IDs
|
|
11
|
+
- fix: inline type imports across test files to comply with no-inline-imports rule
|
|
12
|
+
- chore: extract shared prompt strings into prompts.ts to eliminate duplication
|
|
13
|
+
- chore: fix release workflow output mapping in create-release job
|
|
14
|
+
|
|
15
|
+
## [0.2.0] - 2026-04-11
|
|
16
|
+
|
|
17
|
+
- feat: add dynamic quest usage nudges via context hook
|
|
18
|
+
- feat: inject quest management reminders into system prompt
|
|
19
|
+
- feat: render targeted quest results and add parameter descriptions
|
|
20
|
+
- chore: add npm release flow
|
|
21
|
+
- chore: refactor codebase for improved maintainability
|
|
22
|
+
|
|
5
23
|
## [0.1.0] - 2026-04-10
|
|
6
24
|
|
|
7
25
|
- feat: add session-scoped quest log with tools, commands, and TUI widgets
|
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
|
|
|
@@ -10,34 +10,51 @@ A quest-log for your [pi](https://github.com/badlogic/pi-mono/tree/main/packages
|
|
|
10
10
|
|
|
11
11
|
Long agent sessions drift. Goals get lost in tool calls, side quests multiply, and the original task is forgotten under a pile of yak hair.
|
|
12
12
|
|
|
13
|
+

|
|
14
|
+
|
|
13
15
|
pi-quests gives your agent a persistent quest log — a living TODO list for the current session. Each quest is a checkpoint the agent can create, complete, and optionally roll back to if things go sideways.
|
|
14
16
|
|
|
15
17
|
- **Stay focused** — the quest log keeps the original goal visible no matter how deep the rabbit hole goes
|
|
16
|
-
- **Checkpoint progress** — mark milestones as quests and capture lightweight session snapshots
|
|
17
|
-
- **Rollback safety** — revert to a previous quest snapshot when exploration goes off track
|
|
18
|
+
- **Checkpoint progress** — mark milestones as quests and capture lightweight session snapshots (WIP)
|
|
19
|
+
- **Rollback safety** — revert to a previous quest snapshot when exploration goes off track (WIP)
|
|
18
20
|
- **Session-scoped** — quests live with the session, vanish when you start fresh
|
|
19
21
|
|
|
20
22
|
## Install
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
```bash
|
|
25
|
+
pi install npm:pi-quests
|
|
26
|
+
```
|
|
23
27
|
|
|
24
28
|
## Quick start
|
|
25
29
|
|
|
26
|
-
**See the extension version:**
|
|
27
|
-
```bash
|
|
28
|
-
/quests version
|
|
29
30
|
```
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
/quests help
|
|
32
|
+
|
|
33
|
+
Available /quests subcommands:
|
|
34
|
+
add <description> - Add a new quest
|
|
35
|
+
list - List all quests
|
|
36
|
+
toggle <id> - Toggle quest completion
|
|
37
|
+
delete <id> - Delete a quest
|
|
38
|
+
update <id> <desc> - Update a quest description
|
|
39
|
+
revert - Revert the last quest change
|
|
40
|
+
clear - Clear all quests
|
|
41
|
+
version - Show version
|
|
42
|
+
changelog - Show changelog
|
|
43
|
+
h, help - Show this help message
|
|
34
44
|
```
|
|
35
45
|
|
|
46
|
+

|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
|
|
36
50
|
## Documentation
|
|
37
51
|
|
|
38
52
|
| Doc | Description |
|
|
39
53
|
|-----|-------------|
|
|
40
|
-
| [
|
|
54
|
+
| [Pattern](docs/pattern.md) | "How do I...?" recipes for common workflows |
|
|
55
|
+
| [Quests](docs/quests.md) | What are quests? |
|
|
56
|
+
| [Reference](docs/reference.md) | Complete tool and command schemas, types |
|
|
57
|
+
| [Architecture](docs/architecture.md) | Module map, data flow diagrams, design decisions |
|
|
41
58
|
| [Changelog](CHANGELOG.md) | Version history |
|
|
42
59
|
|
|
43
60
|
## License
|
package/package.json
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reverse changelog sections so the newest release appears first.
|
|
3
|
+
*/
|
|
4
|
+
export function reverseChangelog(content: string): string {
|
|
5
|
+
const parts = content.split(/^## \[/m);
|
|
6
|
+
const preamble = parts[0];
|
|
7
|
+
const sections = parts.slice(1).map((s) => `## [${s}`);
|
|
8
|
+
return [preamble, ...sections.reverse()].join("");
|
|
9
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { logger } from "../logger.js";
|
|
4
|
+
import type { QuestAction, QuestLog } from "../quest/dataplane.js";
|
|
5
|
+
import { QUEST_ACTIONS } from "../quest/types.js";
|
|
6
|
+
import { QuestListWidget } from "../renderers/commands.js";
|
|
7
|
+
import { CHANGELOG_PATH, getVersion } from "../version.js";
|
|
8
|
+
import { reverseChangelog } from "./changelog.js";
|
|
9
|
+
import { type ParsedArgs, parseQuestArgs } from "./parse-args.js";
|
|
10
|
+
|
|
11
|
+
type MutatingCommand = Extract<
|
|
12
|
+
ParsedArgs,
|
|
13
|
+
{
|
|
14
|
+
action:
|
|
15
|
+
| typeof QUEST_ACTIONS.add
|
|
16
|
+
| typeof QUEST_ACTIONS.toggle
|
|
17
|
+
| typeof QUEST_ACTIONS.update
|
|
18
|
+
| typeof QUEST_ACTIONS.delete
|
|
19
|
+
| typeof QUEST_ACTIONS.clear
|
|
20
|
+
| typeof QUEST_ACTIONS.reorder
|
|
21
|
+
| typeof QUEST_ACTIONS.revert;
|
|
22
|
+
}
|
|
23
|
+
>["action"];
|
|
24
|
+
|
|
25
|
+
const commandActionBuilders: {
|
|
26
|
+
[K in MutatingCommand]: (parsed: Extract<ParsedArgs, { action: K }>) => QuestAction;
|
|
27
|
+
} = {
|
|
28
|
+
[QUEST_ACTIONS.add]: (p) => ({ type: QUEST_ACTIONS.add, descriptions: p.descriptions }),
|
|
29
|
+
[QUEST_ACTIONS.toggle]: (p) => ({ type: QUEST_ACTIONS.toggle, id: p.id }),
|
|
30
|
+
[QUEST_ACTIONS.update]: (p) => ({
|
|
31
|
+
type: QUEST_ACTIONS.update,
|
|
32
|
+
id: p.id,
|
|
33
|
+
description: p.description,
|
|
34
|
+
}),
|
|
35
|
+
[QUEST_ACTIONS.delete]: (p) => ({ type: QUEST_ACTIONS.delete, id: p.id }),
|
|
36
|
+
[QUEST_ACTIONS.clear]: (p) => ({ type: QUEST_ACTIONS.clear, all: p.all }),
|
|
37
|
+
[QUEST_ACTIONS.reorder]: (p) => ({
|
|
38
|
+
type: QUEST_ACTIONS.reorder,
|
|
39
|
+
id: p.id,
|
|
40
|
+
targetIndex: p.targetIndex,
|
|
41
|
+
}),
|
|
42
|
+
[QUEST_ACTIONS.revert]: () => ({ type: QUEST_ACTIONS.revert }),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function createQuestsHandler(pi: ExtensionAPI, questLog: QuestLog) {
|
|
46
|
+
return async function handler(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
47
|
+
logger.debug("quests:cmd", "handler", { args, hasUI: ctx.hasUI });
|
|
48
|
+
|
|
49
|
+
const parsed = parseQuestArgs(args);
|
|
50
|
+
if ("error" in parsed) {
|
|
51
|
+
logger.debug("quests:cmd", "handler-error", { error: parsed.error });
|
|
52
|
+
ctx.ui.notify(parsed.error, "error");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
switch (parsed.action) {
|
|
57
|
+
case "version": {
|
|
58
|
+
const version = getVersion();
|
|
59
|
+
logger.debug("quests:cmd", "version", { version });
|
|
60
|
+
ctx.ui.notify(`pi-quests v${version}`, "info");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
case "changelog": {
|
|
64
|
+
logger.debug("quests:cmd", "changelog", { changelogPath: CHANGELOG_PATH });
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const content = readFileSync(CHANGELOG_PATH, "utf-8");
|
|
68
|
+
logger.debug("quests:cmd", "changelog-read", { contentLength: content.length });
|
|
69
|
+
|
|
70
|
+
const reversedContent = reverseChangelog(content);
|
|
71
|
+
logger.debug("quests:cmd", "changelog-reversed");
|
|
72
|
+
|
|
73
|
+
pi.sendMessage({
|
|
74
|
+
customType: "quest-changelog",
|
|
75
|
+
content: "",
|
|
76
|
+
display: true,
|
|
77
|
+
details: { content: reversedContent },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
logger.debug("quests:cmd", "changelog-sent");
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
83
|
+
|
|
84
|
+
logger.debug("quests:cmd", "changelog-error", { error: errorMessage });
|
|
85
|
+
ctx.ui.notify(`Failed to read changelog: ${errorMessage}`, "error");
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
case QUEST_ACTIONS.list: {
|
|
90
|
+
if (!ctx.hasUI) {
|
|
91
|
+
logger.debug("quests:cmd", "list-no-ui");
|
|
92
|
+
ctx.ui.notify("Interactive mode required", "error");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
logger.debug("quests:cmd", "list-open-widget", { count: questLog.getAll().length });
|
|
97
|
+
await ctx.ui.custom(
|
|
98
|
+
(_, theme, __, done) =>
|
|
99
|
+
new QuestListWidget(questLog.getAll(), theme, () => done(undefined)),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
logger.debug("quests:cmd", "list-widget-closed");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
case QUEST_ACTIONS.add:
|
|
106
|
+
case QUEST_ACTIONS.toggle:
|
|
107
|
+
case QUEST_ACTIONS.update:
|
|
108
|
+
case QUEST_ACTIONS.delete:
|
|
109
|
+
case QUEST_ACTIONS.clear:
|
|
110
|
+
case QUEST_ACTIONS.reorder:
|
|
111
|
+
case QUEST_ACTIONS.revert: {
|
|
112
|
+
const builder = commandActionBuilders[parsed.action];
|
|
113
|
+
const action = builder(parsed as never);
|
|
114
|
+
const result = questLog.execute(action);
|
|
115
|
+
|
|
116
|
+
logger.debug("quests:cmd", parsed.action, { success: result.success });
|
|
117
|
+
ctx.ui.notify(result.message, result.success ? "info" : "error");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
case "help": {
|
|
121
|
+
logger.debug("quests:cmd", "help");
|
|
122
|
+
|
|
123
|
+
const lines = ["Available /quests subcommands:"];
|
|
124
|
+
lines.push(" add <description> - Add a new quest");
|
|
125
|
+
lines.push(" list - List all quests");
|
|
126
|
+
lines.push(" toggle <id> - Toggle quest completion");
|
|
127
|
+
lines.push(" delete <id> - Delete a quest");
|
|
128
|
+
lines.push(" update <id> <desc> - Update a quest description");
|
|
129
|
+
lines.push(" reorder <id> <idx> - Reorder a quest to index");
|
|
130
|
+
lines.push(" revert - Revert the last quest change");
|
|
131
|
+
lines.push(" clear [all] - Clear completed quests, or optionally all quests");
|
|
132
|
+
lines.push(" version - Show version");
|
|
133
|
+
lines.push(" changelog - Show changelog");
|
|
134
|
+
lines.push(" h, help - Show this help message");
|
|
135
|
+
|
|
136
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
137
|
+
logger.debug("quests:cmd", "help-complete");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { logger } from "../logger.js";
|
|
2
|
+
import { QUEST_ACTIONS } from "../quest/types.js";
|
|
3
|
+
|
|
4
|
+
export type ParsedArgs =
|
|
5
|
+
| { action: typeof QUEST_ACTIONS.add; descriptions: string[] }
|
|
6
|
+
| { action: typeof QUEST_ACTIONS.list }
|
|
7
|
+
| { action: typeof QUEST_ACTIONS.toggle; id: number }
|
|
8
|
+
| { action: typeof QUEST_ACTIONS.update; id: number; description: string }
|
|
9
|
+
| { action: typeof QUEST_ACTIONS.delete; id: number }
|
|
10
|
+
| { action: typeof QUEST_ACTIONS.clear; all?: boolean }
|
|
11
|
+
| { action: typeof QUEST_ACTIONS.reorder; id: number; targetIndex: number }
|
|
12
|
+
| { action: typeof QUEST_ACTIONS.revert }
|
|
13
|
+
| { action: "help" }
|
|
14
|
+
| { action: "version" }
|
|
15
|
+
| { action: "changelog" }
|
|
16
|
+
| { error: string };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parse user input from the /quests command into structured arguments.
|
|
20
|
+
*/
|
|
21
|
+
export function parseQuestArgs(args: string): ParsedArgs {
|
|
22
|
+
logger.debug("quests:cmd", "parse-args", { args });
|
|
23
|
+
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
24
|
+
|
|
25
|
+
// Empty input defaults to listing quests.
|
|
26
|
+
if (tokens.length === 0) {
|
|
27
|
+
logger.debug("quests:cmd", "parse-args-empty", { action: QUEST_ACTIONS.list });
|
|
28
|
+
return { action: QUEST_ACTIONS.list };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const [command, ...rest] = tokens;
|
|
32
|
+
|
|
33
|
+
// Meta commands
|
|
34
|
+
if (command === "version") return { action: "version" };
|
|
35
|
+
if (command === "changelog") return { action: "changelog" };
|
|
36
|
+
if (command === "help" || command === "h") return { action: "help" };
|
|
37
|
+
|
|
38
|
+
// Quest actions without arguments
|
|
39
|
+
if (command === QUEST_ACTIONS.list) return { action: QUEST_ACTIONS.list };
|
|
40
|
+
if (command === QUEST_ACTIONS.clear) return parseClearArgs(rest);
|
|
41
|
+
if (command === QUEST_ACTIONS.revert) return { action: QUEST_ACTIONS.revert };
|
|
42
|
+
|
|
43
|
+
// Quest actions with arguments
|
|
44
|
+
if (command === QUEST_ACTIONS.add) return parseAddArgs(rest);
|
|
45
|
+
if (command === QUEST_ACTIONS.toggle) return parseIdAction(QUEST_ACTIONS.toggle, rest);
|
|
46
|
+
if (command === QUEST_ACTIONS.delete) return parseIdAction(QUEST_ACTIONS.delete, rest);
|
|
47
|
+
if (command === QUEST_ACTIONS.update) return parseUpdateArgs(rest);
|
|
48
|
+
if (command === QUEST_ACTIONS.reorder) return parseReorderArgs(rest);
|
|
49
|
+
|
|
50
|
+
return { error: `Unknown subcommand: ${command}. Use /quests help to see available commands.` };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseAddArgs(tokens: string[]): ParsedArgs {
|
|
54
|
+
const description = tokens.join(" ").trim();
|
|
55
|
+
if (!description) return { error: "Usage: /quests add <description>" };
|
|
56
|
+
|
|
57
|
+
return { action: QUEST_ACTIONS.add, descriptions: [description] };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseIdAction(
|
|
61
|
+
action: typeof QUEST_ACTIONS.toggle | typeof QUEST_ACTIONS.delete,
|
|
62
|
+
tokens: string[],
|
|
63
|
+
): ParsedArgs {
|
|
64
|
+
const idStr = tokens[0];
|
|
65
|
+
const id = idStr ? Number(idStr) : NaN;
|
|
66
|
+
if (Number.isNaN(id)) return { error: `Usage: /quests ${action} <id>` };
|
|
67
|
+
|
|
68
|
+
return { action, id };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseUpdateArgs(tokens: string[]): ParsedArgs {
|
|
72
|
+
const idStr = tokens[0];
|
|
73
|
+
const id = idStr ? Number(idStr) : NaN;
|
|
74
|
+
if (Number.isNaN(id)) return { error: "Usage: /quests update <id> <description>" };
|
|
75
|
+
|
|
76
|
+
const description = tokens.slice(1).join(" ").trim();
|
|
77
|
+
if (!description) return { error: "Usage: /quests update <id> <description>" };
|
|
78
|
+
|
|
79
|
+
return { action: QUEST_ACTIONS.update, id, description };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseClearArgs(tokens: string[]): ParsedArgs {
|
|
83
|
+
const all = tokens[0] === "all";
|
|
84
|
+
if (tokens.length > 0 && !all) return { error: "Usage: /quests clear [all]" };
|
|
85
|
+
return { action: QUEST_ACTIONS.clear, all };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseReorderArgs(tokens: string[]): ParsedArgs {
|
|
89
|
+
const id = Number(tokens[0]);
|
|
90
|
+
const targetIndex = Number(tokens[1]);
|
|
91
|
+
if (Number.isNaN(id) || Number.isNaN(targetIndex))
|
|
92
|
+
return { error: "Usage: /quests reorder <id> <index>" };
|
|
93
|
+
return { action: QUEST_ACTIONS.reorder, id, targetIndex };
|
|
94
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { UserMessage } from "@mariozechner/pi-ai";
|
|
1
2
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { createQuestsHandler } from "./commands/
|
|
3
|
-
import {
|
|
3
|
+
import { createQuestsHandler } from "./commands/handler.js";
|
|
4
|
+
import { QUEST_PROMPT_GATE, QUEST_PROMPT_REMINDER } from "./prompts.js";
|
|
5
|
+
import { QuestLog } from "./quest/dataplane.js";
|
|
6
|
+
import { QuestUsageTracker } from "./quest/tracker.js";
|
|
4
7
|
import { questChangelogRenderer } from "./renderers/changelog.js";
|
|
5
|
-
import { registerQuestTool } from "./tools/
|
|
8
|
+
import { registerQuestTool } from "./tools/handler.js";
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* pi-quests
|
|
@@ -16,10 +19,61 @@ import { registerQuestTool } from "./tools/quest.js";
|
|
|
16
19
|
*/
|
|
17
20
|
export default function (pi: ExtensionAPI): void {
|
|
18
21
|
const questLog = new QuestLog();
|
|
22
|
+
const tracker = new QuestUsageTracker();
|
|
19
23
|
|
|
20
24
|
pi.on("session_start", async (_event, ctx) => questLog.reconstructFromSession(ctx));
|
|
21
25
|
pi.on("session_tree", async (_event, ctx) => questLog.reconstructFromSession(ctx));
|
|
22
26
|
|
|
27
|
+
pi.on("turn_start", async () => tracker.clearTurnNudge());
|
|
28
|
+
|
|
29
|
+
pi.on("tool_execution_end", async (event) => {
|
|
30
|
+
tracker.onToolExecution(event.toolName);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
pi.on("context", async (event) => {
|
|
34
|
+
const latestPrompt = event.messages
|
|
35
|
+
.filter((m) => m.role === "user")
|
|
36
|
+
.map((m) => (typeof m.content === "string" ? m.content : ""))
|
|
37
|
+
.join("\n");
|
|
38
|
+
|
|
39
|
+
const activeQuestCount = questLog.getAll().filter((q) => !q.done).length;
|
|
40
|
+
const nudge = tracker.getNudge(activeQuestCount, latestPrompt);
|
|
41
|
+
|
|
42
|
+
const fakeDoneRegex =
|
|
43
|
+
/\s[-–—]\s*(DONE|COMPLETED|FINISHED)$|\s[([](DONE|COMPLETED|FINISHED)[)\]]$/i;
|
|
44
|
+
const fakeDone = questLog.getAll().find((q) => !q.done && fakeDoneRegex.test(q.description));
|
|
45
|
+
if (!nudge && !fakeDone) return undefined;
|
|
46
|
+
|
|
47
|
+
let content = nudge ?? "";
|
|
48
|
+
if (fakeDone) {
|
|
49
|
+
content += `\nQUEST REMINDER: Quest #${fakeDone.id} has a completion marker appended to its description but is not toggled done. Use the toggle action to mark it done. NEVER append completion markers to descriptions via the update action.`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const reminder: UserMessage = {
|
|
53
|
+
role: "user",
|
|
54
|
+
content: content.trim(),
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
};
|
|
57
|
+
return { messages: [...event.messages, reminder] };
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
pi.on("before_agent_start", async (event) => {
|
|
61
|
+
const quests = questLog.getAll();
|
|
62
|
+
let reminder = QUEST_PROMPT_REMINDER.join("\n");
|
|
63
|
+
if (quests.length > 0) {
|
|
64
|
+
const remaining = quests.filter((q) => !q.done).length;
|
|
65
|
+
const list = quests
|
|
66
|
+
.map((q) => `#${q.id} [${q.done ? "x" : " "}] ${q.description}`)
|
|
67
|
+
.join("\n");
|
|
68
|
+
|
|
69
|
+
reminder += `\n\nActive quests (${remaining}/${quests.length}):\n${list}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
systemPrompt: `# Quest Management\n${QUEST_PROMPT_GATE}${event.systemPrompt}\n\n## Quest Management\n${reminder}`,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
23
77
|
registerQuestTool(pi, questLog);
|
|
24
78
|
|
|
25
79
|
// Register custom message renderers
|
package/src/logger.ts
CHANGED
|
@@ -3,13 +3,22 @@ import { dirname } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
export const LOG_FILE = "/tmp/logs/pi-quests/debug.log";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
let stream: ReturnType<typeof createWriteStream> | undefined;
|
|
7
|
+
|
|
8
|
+
function getStream() {
|
|
9
|
+
if (!stream) {
|
|
10
|
+
mkdirSync(dirname(LOG_FILE), { recursive: true });
|
|
11
|
+
stream = createWriteStream(LOG_FILE, { flags: "a" });
|
|
12
|
+
stream.on("error", () => {});
|
|
13
|
+
}
|
|
14
|
+
return stream;
|
|
15
|
+
}
|
|
7
16
|
|
|
8
17
|
export const logger = {
|
|
9
18
|
debug: (namespace: string, event: string, meta?: Record<string, unknown>) => {
|
|
10
19
|
const timestamp = new Date().toISOString();
|
|
11
20
|
const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
|
|
12
21
|
const line = `[${timestamp}] [${namespace}] ${event}${metaStr}\n`;
|
|
13
|
-
|
|
22
|
+
getStream().write(line);
|
|
14
23
|
},
|
|
15
24
|
};
|
package/src/prompts.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const QUEST_PROMPT_REMINDER = [
|
|
2
|
+
"Use the quest tool VERY frequently to track tasks, plans, and progress throughout the conversation.",
|
|
3
|
+
"Before reading files, running commands, or making edits, ensure the current work is tracked as specific, actionable quests.",
|
|
4
|
+
"Do not create a single vague quest for broad requests. Break them into concrete, independent steps.",
|
|
5
|
+
"When the user gives a plan or a list of tasks, add them as quests immediately.",
|
|
6
|
+
"When reading a skill file, implementation plan, or protocol document that contains numbered steps or checklists, add those steps as quests immediately so they are tracked, and reorder them as needed in the quest log.",
|
|
7
|
+
"It is critical that you toggle quests to done as soon as you complete them. Do NOT batch completions.",
|
|
8
|
+
"ALWAYS use the toggle action to mark a quest done. NEVER use the update action to append 'DONE', '- DONE', or any completion marker to a quest description.",
|
|
9
|
+
"As work evolves, use the reorder action to reflect changes in priority or sequencing.",
|
|
10
|
+
"Before delegating to a minion, add a quest for the delegated task.",
|
|
11
|
+
"For reorder, targetIndex is 0-based (array index), not a 1-based position. If the user says 'move quest to position 5', use targetIndex 4.",
|
|
12
|
+
"If you are unsure what to do next, use the list action to check active quests.",
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
export const QUEST_PROMPT_GATE =
|
|
16
|
+
"Before reading files, running commands, or making edits, ALWAYS ensure the current work is tracked as specific, actionable quests. ALWAYS break broad requests into concrete steps.";
|