pi-quests 0.3.0 → 0.5.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 +16 -0
- package/README.md +20 -1
- package/package.json +1 -1
- package/src/commands/handler.ts +43 -11
- package/src/commands/parse-args.ts +51 -22
- package/src/config.ts +148 -0
- package/src/index.ts +55 -23
- package/src/prompts/skill.md +60 -0
- package/src/prompts.ts +16 -2
- package/src/quest/dataplane.ts +473 -104
- package/src/quest/formatters.ts +119 -16
- package/src/quest/tracker.ts +99 -39
- package/src/quest/types.ts +15 -2
- package/src/renderers/commands.ts +46 -24
- package/src/renderers/quests.ts +45 -0
- package/src/renderers/tools.ts +80 -31
- package/src/tools/handler.ts +59 -16
- package/src/tools/params.ts +44 -30
package/src/renderers/tools.ts
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import type { AgentToolResult, Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { Text } from "@mariozechner/pi-tui";
|
|
3
|
+
import type { ResolvedConfig } from "../config.js";
|
|
3
4
|
import { logger } from "../logger.js";
|
|
5
|
+
import type { Quest } from "../quest/types.js";
|
|
4
6
|
import { QUEST_ACTIONS } from "../quest/types.js";
|
|
7
|
+
import { formatQuestRow, formatStepSpacerLine } from "./quests.js";
|
|
5
8
|
|
|
6
9
|
type QuestArgs = {
|
|
7
10
|
action: string;
|
|
8
11
|
descriptions?: string[];
|
|
9
|
-
|
|
12
|
+
targetId?: string;
|
|
13
|
+
id?: string;
|
|
14
|
+
all?: boolean;
|
|
10
15
|
};
|
|
11
16
|
|
|
12
17
|
export function renderQuestCall(args: QuestArgs, theme: Theme, _context: unknown): Text {
|
|
13
18
|
const id = "id" in args ? args.id : undefined;
|
|
14
19
|
logger.debug("quests:tool", "renderCall", { action: args.action, id });
|
|
15
|
-
const actionText =
|
|
20
|
+
const actionText =
|
|
21
|
+
theme.fg("toolTitle", theme.bold("quest ")) +
|
|
22
|
+
theme.fg("accent", args.action) +
|
|
23
|
+
theme.fg("muted", args.targetId ? ` ${args.targetId}` : "");
|
|
16
24
|
|
|
17
25
|
if (args.action === QUEST_ACTIONS.add && args.descriptions && args.descriptions.length > 0) {
|
|
18
26
|
const n = args.descriptions.length;
|
|
@@ -29,41 +37,82 @@ export function renderQuestCall(args: QuestArgs, theme: Theme, _context: unknown
|
|
|
29
37
|
args.action === QUEST_ACTIONS.delete) &&
|
|
30
38
|
args.id !== undefined
|
|
31
39
|
) {
|
|
32
|
-
return new Text(`${actionText} ${theme.fg("muted",
|
|
40
|
+
return new Text(`${actionText} ${theme.fg("muted", `[${args.id}]`)}`, 0, 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (args.action === QUEST_ACTIONS.clear) {
|
|
44
|
+
return new Text(`${actionText} ${theme.fg("muted", `[${args.all ? "all" : "done"}]`)}`, 0, 0);
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
return new Text(actionText, 0, 0);
|
|
36
48
|
}
|
|
37
49
|
|
|
38
|
-
export function renderQuestResult(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const details = result.details as Record<string, unknown> | undefined;
|
|
49
|
-
const allQuests =
|
|
50
|
-
(details?.quests as Array<{ id: number; description: string; done: boolean }> | undefined) ??
|
|
51
|
-
[];
|
|
52
|
-
const questsToRender = (details?.displayQuests ?? details?.quests) as
|
|
53
|
-
| Array<{ id: number; description: string; done: boolean }>
|
|
54
|
-
| undefined;
|
|
55
|
-
|
|
56
|
-
if (Array.isArray(questsToRender) && questsToRender.length > 0) {
|
|
57
|
-
const lines = questsToRender.map((q) => {
|
|
58
|
-
const pos = allQuests.findIndex((x) => x.id === q.id) + 1 || 1;
|
|
59
|
-
const marker = q.done ? theme.fg("success", "✓") : theme.fg("dim", "○");
|
|
60
|
-
|
|
61
|
-
return `${marker} ${theme.fg("text", `#${pos}`)} ${theme.fg("muted", q.description)}`;
|
|
50
|
+
export function renderQuestResult(config: ResolvedConfig) {
|
|
51
|
+
return (
|
|
52
|
+
result: AgentToolResult<unknown>,
|
|
53
|
+
options: { expanded: boolean; isPartial: boolean },
|
|
54
|
+
theme: Theme,
|
|
55
|
+
_context: unknown,
|
|
56
|
+
): Text => {
|
|
57
|
+
logger.debug("quests:tool", "renderResult", {
|
|
58
|
+
expanded: options.expanded,
|
|
59
|
+
isPartial: options.isPartial,
|
|
62
60
|
});
|
|
61
|
+
const details = result.details as Record<string, unknown> | undefined;
|
|
62
|
+
const allQuests = details?.quests as Quest[] | undefined;
|
|
63
|
+
const questsToRender = (details?.displayQuests ?? details?.quests) as Quest[] | undefined;
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
if (
|
|
66
|
+
!Array.isArray(questsToRender) ||
|
|
67
|
+
questsToRender.length === 0 ||
|
|
68
|
+
!Array.isArray(allQuests)
|
|
69
|
+
) {
|
|
70
|
+
const text = result.content.map((c) => (c.type === "text" ? c.text : "[image]")).join("\n");
|
|
71
|
+
return new Text(theme.fg("text", text), 0, 0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const quests = allQuests;
|
|
75
|
+
const renderedIds = new Set(questsToRender.map((q) => q.id));
|
|
76
|
+
const parents = quests.filter((q) => !(q as Quest & { parentId?: string }).parentId);
|
|
77
|
+
const lines: string[] = [];
|
|
78
|
+
|
|
79
|
+
function getSteps(parentId: string) {
|
|
80
|
+
return quests.filter((q) => (q as Quest & { parentId?: string }).parentId === parentId);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function willRenderLater(idx: number): boolean {
|
|
84
|
+
for (let j = idx + 1; j < parents.length; j++) {
|
|
85
|
+
const p = parents[j];
|
|
86
|
+
if (renderedIds.has(p.id)) return true;
|
|
87
|
+
|
|
88
|
+
const steps = getSteps(p.id).filter((step) => renderedIds.has(step.id));
|
|
89
|
+
if (steps.length > 0) return true;
|
|
90
|
+
}
|
|
66
91
|
|
|
67
|
-
|
|
68
|
-
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < parents.length; i++) {
|
|
96
|
+
const parent = parents[i];
|
|
97
|
+
const parentIncluded = renderedIds.has(parent.id);
|
|
98
|
+
const includedSteps = getSteps(parent.id).filter((step) => renderedIds.has(step.id));
|
|
99
|
+
|
|
100
|
+
if (!parentIncluded && includedSteps.length === 0) continue;
|
|
101
|
+
|
|
102
|
+
if (parentIncluded) {
|
|
103
|
+
lines.push(formatQuestRow(theme, parent, config.ids.length, i + 1));
|
|
104
|
+
if (includedSteps.length > 0) lines.push(formatStepSpacerLine(theme, config.ids.length));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const step of includedSteps) {
|
|
108
|
+
lines.push(formatQuestRow(theme, step, config.ids.length));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (includedSteps.length > 0 && willRenderLater(i)) {
|
|
112
|
+
lines.push("");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
117
|
+
};
|
|
69
118
|
}
|
package/src/tools/handler.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import type { AgentToolResult, ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import type { Static } from "@sinclair/typebox";
|
|
3
|
+
import type { ResolvedConfig } from "../config.js";
|
|
3
4
|
import { logger } from "../logger.js";
|
|
4
5
|
import { QUEST_PROMPT_GATE, QUEST_PROMPT_REMINDER } from "../prompts.js";
|
|
5
6
|
import { makeToolResult, type QuestAction, type QuestLog } from "../quest/dataplane.js";
|
|
6
7
|
import { QUEST_ACTIONS } from "../quest/types.js";
|
|
8
|
+
|
|
9
|
+
const SPLIT_DISPLAY_ACTIONS = [
|
|
10
|
+
QUEST_ACTIONS.add,
|
|
11
|
+
QUEST_ACTIONS.list,
|
|
12
|
+
QUEST_ACTIONS.split,
|
|
13
|
+
QUEST_ACTIONS.add_step,
|
|
14
|
+
QUEST_ACTIONS.revert,
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
7
17
|
import { renderQuestCall, renderQuestResult } from "../renderers/tools.js";
|
|
8
|
-
import {
|
|
18
|
+
import { createQuestParams, type QuestParamsType } from "./params.js";
|
|
9
19
|
|
|
10
|
-
type QuestToolParams = Static<
|
|
20
|
+
type QuestToolParams = Static<QuestParamsType>;
|
|
11
21
|
|
|
12
22
|
const toolHandlers: {
|
|
13
23
|
[K in QuestToolParams["action"]]: (
|
|
@@ -22,6 +32,20 @@ const toolHandlers: {
|
|
|
22
32
|
descriptions: params.descriptions,
|
|
23
33
|
});
|
|
24
34
|
},
|
|
35
|
+
[QUEST_ACTIONS.split](questLog, params, toolCallId) {
|
|
36
|
+
return runTool(questLog, toolCallId, {
|
|
37
|
+
type: QUEST_ACTIONS.split,
|
|
38
|
+
id: params.id,
|
|
39
|
+
descriptions: params.descriptions,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
[QUEST_ACTIONS.add_step](questLog, params, toolCallId) {
|
|
43
|
+
return runTool(questLog, toolCallId, {
|
|
44
|
+
type: QUEST_ACTIONS.add_step,
|
|
45
|
+
id: params.id,
|
|
46
|
+
descriptions: params.descriptions,
|
|
47
|
+
});
|
|
48
|
+
},
|
|
25
49
|
[QUEST_ACTIONS.list](questLog, _params, toolCallId) {
|
|
26
50
|
return runTool(questLog, toolCallId, { type: QUEST_ACTIONS.list });
|
|
27
51
|
},
|
|
@@ -45,9 +69,22 @@ const toolHandlers: {
|
|
|
45
69
|
return runTool(questLog, toolCallId, {
|
|
46
70
|
type: QUEST_ACTIONS.reorder,
|
|
47
71
|
id: params.id,
|
|
48
|
-
|
|
72
|
+
targetId: params.targetId,
|
|
49
73
|
});
|
|
50
74
|
},
|
|
75
|
+
[QUEST_ACTIONS.reparent](questLog, params, toolCallId) {
|
|
76
|
+
return runTool(questLog, toolCallId, {
|
|
77
|
+
type: QUEST_ACTIONS.reparent,
|
|
78
|
+
id: params.id,
|
|
79
|
+
parentId: params.parentId,
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
[QUEST_ACTIONS.rules](questLog, _params, toolCallId) {
|
|
83
|
+
return runTool(questLog, toolCallId, { type: QUEST_ACTIONS.rules });
|
|
84
|
+
},
|
|
85
|
+
[QUEST_ACTIONS.skill](questLog, _params, toolCallId) {
|
|
86
|
+
return runTool(questLog, toolCallId, { type: QUEST_ACTIONS.skill });
|
|
87
|
+
},
|
|
51
88
|
[QUEST_ACTIONS.revert](questLog, _params, toolCallId) {
|
|
52
89
|
return runTool(questLog, toolCallId, { type: QUEST_ACTIONS.revert });
|
|
53
90
|
},
|
|
@@ -61,14 +98,13 @@ function runTool(
|
|
|
61
98
|
const result = questLog.execute(action);
|
|
62
99
|
logger.debug("quests:tool", "execute-complete", { toolCallId, success: result.success });
|
|
63
100
|
|
|
64
|
-
const displayQuests =
|
|
65
|
-
action.type
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
: undefined;
|
|
101
|
+
const displayQuests = SPLIT_DISPLAY_ACTIONS.includes(
|
|
102
|
+
action.type as (typeof SPLIT_DISPLAY_ACTIONS)[number],
|
|
103
|
+
)
|
|
104
|
+
? questLog.getAll()
|
|
105
|
+
: result.quest
|
|
106
|
+
? [result.quest]
|
|
107
|
+
: undefined;
|
|
72
108
|
|
|
73
109
|
return makeToolResult(result.message, questLog, displayQuests);
|
|
74
110
|
}
|
|
@@ -83,19 +119,26 @@ export async function questToolExecute(
|
|
|
83
119
|
return handler(questLog, params, toolCallId);
|
|
84
120
|
}
|
|
85
121
|
|
|
86
|
-
export function registerQuestTool(
|
|
122
|
+
export function registerQuestTool(
|
|
123
|
+
pi: ExtensionAPI,
|
|
124
|
+
questLog: QuestLog,
|
|
125
|
+
config: ResolvedConfig,
|
|
126
|
+
): void {
|
|
87
127
|
logger.debug("quests:tool", "register");
|
|
88
128
|
pi.registerTool({
|
|
89
129
|
name: "quest",
|
|
90
130
|
label: "Quest",
|
|
91
131
|
description:
|
|
92
|
-
"Manage the session quest log
|
|
93
|
-
|
|
132
|
+
"Manage the session quest log and retrieve the complete quest system documentation. " +
|
|
133
|
+
"Use this VERY frequently to track tasks, plans, and progress. " +
|
|
134
|
+
"When you need to understand quests, steps, rules, or best practices, use action: 'skill' or action: 'rules'.",
|
|
135
|
+
promptSnippet:
|
|
136
|
+
"Manage quests and steps, or retrieve quest rules and best practices via skill/rules",
|
|
94
137
|
promptGuidelines: [...QUEST_PROMPT_GATE, ...QUEST_PROMPT_REMINDER],
|
|
95
|
-
parameters:
|
|
138
|
+
parameters: createQuestParams(config.ids.length),
|
|
96
139
|
execute: (toolCallId, params, _signal, _onUpdate, _ctx) =>
|
|
97
140
|
questToolExecute(questLog, toolCallId, params),
|
|
98
141
|
renderCall: renderQuestCall,
|
|
99
|
-
renderResult: renderQuestResult,
|
|
142
|
+
renderResult: renderQuestResult(config),
|
|
100
143
|
});
|
|
101
144
|
}
|
package/src/tools/params.ts
CHANGED
|
@@ -2,34 +2,48 @@ import { StringEnum } from "@mariozechner/pi-ai";
|
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
3
|
import { QUEST_ACTION_VALUES } from "../quest/types.js";
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Type.Array(Type.String(), {
|
|
11
|
-
description: "Array of quest descriptions (required for add action)",
|
|
5
|
+
export function createQuestParams(idLength: number) {
|
|
6
|
+
const pattern = `^[0-9a-f]{${idLength}}$`;
|
|
7
|
+
return Type.Object({
|
|
8
|
+
action: StringEnum(QUEST_ACTION_VALUES, {
|
|
9
|
+
description: "The quest action to perform",
|
|
12
10
|
}),
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
})
|
|
11
|
+
descriptions: Type.Optional(
|
|
12
|
+
Type.Array(Type.String(), {
|
|
13
|
+
description: "Array of quest descriptions (required for add action)",
|
|
14
|
+
}),
|
|
15
|
+
),
|
|
16
|
+
description: Type.Optional(
|
|
17
|
+
Type.String({
|
|
18
|
+
description: "New description (required for update action)",
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
|
|
22
|
+
id: Type.Optional(
|
|
23
|
+
Type.String({
|
|
24
|
+
pattern,
|
|
25
|
+
description: `Quest ID (required for toggle, update, delete, reorder actions). ALWAYS use the ${idLength}-digit hex ID shown in brackets, never the positional number.`,
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
28
|
+
targetId: Type.Optional(
|
|
29
|
+
Type.String({
|
|
30
|
+
pattern,
|
|
31
|
+
description:
|
|
32
|
+
"Target quest ID for reorder action. The quest will be moved to just before the target quest.",
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
parentId: Type.Optional(
|
|
36
|
+
Type.String({
|
|
37
|
+
pattern,
|
|
38
|
+
description: "Parent quest ID for reparent action. Omit to promote to top-level.",
|
|
39
|
+
}),
|
|
40
|
+
),
|
|
41
|
+
all: Type.Optional(
|
|
42
|
+
Type.Boolean({
|
|
43
|
+
description: "Clear all quests when true (defaults to clearing only completed quests)",
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type QuestParamsType = ReturnType<typeof createQuestParams>;
|