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/src/quest/formatters.ts
CHANGED
|
@@ -44,6 +44,12 @@ export function formatToggleResult(id: string, done: boolean): string {
|
|
|
44
44
|
return `Quest [${id}] ${done ? "done" : "undone"}`;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export function formatBatchToggleResult(toggled: { id: string; done: boolean }[]): string {
|
|
48
|
+
return `Toggled ${toggled.length} tasks:\n${toggled
|
|
49
|
+
.map((quest) => `[${quest.id}] ${quest.done ? "done" : "undone"}`)
|
|
50
|
+
.join("\n")}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
47
53
|
export function formatUpdateResult(q: { id: string; description: string }): string {
|
|
48
54
|
return `Updated quest [${q.id}]: ${q.description}`;
|
|
49
55
|
}
|
|
@@ -52,6 +58,10 @@ export function formatDeleteResult(q: { id: string; description: string }): stri
|
|
|
52
58
|
return `Deleted quest [${q.id}]: ${q.description}`;
|
|
53
59
|
}
|
|
54
60
|
|
|
61
|
+
export function formatBatchDeleteResult(deleted: { id: string; description: string }[]): string {
|
|
62
|
+
return `Deleted ${deleted.length} tasks:\n${deleted.map((quest) => `[${quest.id}] ${quest.description}`).join("\n")}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
export function formatNotFound(id: string): string {
|
|
56
66
|
return `Quest [${id}] not found. IDs are random hex strings shown in brackets. Use the list action to see valid IDs.`;
|
|
57
67
|
}
|
|
@@ -100,8 +110,12 @@ export function formatUnknownActionError(action: string): string {
|
|
|
100
110
|
return `Unknown action: ${action}. Use the list action or check the tool schema for supported actions.`;
|
|
101
111
|
}
|
|
102
112
|
|
|
103
|
-
export function
|
|
104
|
-
return `Nothing to
|
|
113
|
+
export function formatNothingToUndoError(): string {
|
|
114
|
+
return `Nothing to undo. The history is empty because no mutating actions have been performed yet.`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function formatNothingToRedoError(): string {
|
|
118
|
+
return `Nothing to redo. The redo stack is empty because no actions have been undone yet.`;
|
|
105
119
|
}
|
|
106
120
|
|
|
107
121
|
export function formatReorderedQuestNotFoundError(): string {
|
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
|
}
|
package/src/quest/types.ts
CHANGED
|
@@ -10,7 +10,8 @@ export const QUEST_ACTIONS = {
|
|
|
10
10
|
delete: "delete",
|
|
11
11
|
clear: "clear",
|
|
12
12
|
reorder: "reorder",
|
|
13
|
-
|
|
13
|
+
undo: "undo",
|
|
14
|
+
redo: "redo",
|
|
14
15
|
reparent: "reparent",
|
|
15
16
|
rules: "rules",
|
|
16
17
|
skill: "skill",
|
|
@@ -26,7 +27,8 @@ export const QUEST_ACTION_VALUES = [
|
|
|
26
27
|
QUEST_ACTIONS.delete,
|
|
27
28
|
QUEST_ACTIONS.clear,
|
|
28
29
|
QUEST_ACTIONS.reorder,
|
|
29
|
-
QUEST_ACTIONS.
|
|
30
|
+
QUEST_ACTIONS.undo,
|
|
31
|
+
QUEST_ACTIONS.redo,
|
|
30
32
|
QUEST_ACTIONS.reparent,
|
|
31
33
|
QUEST_ACTIONS.rules,
|
|
32
34
|
QUEST_ACTIONS.skill,
|
|
@@ -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";
|
|
@@ -11,6 +11,7 @@ type QuestArgs = {
|
|
|
11
11
|
descriptions?: string[];
|
|
12
12
|
targetId?: string;
|
|
13
13
|
id?: string;
|
|
14
|
+
ids?: string[];
|
|
14
15
|
all?: boolean;
|
|
15
16
|
};
|
|
16
17
|
|
|
@@ -31,12 +32,29 @@ export function renderQuestCall(args: QuestArgs, theme: Theme, _context: unknown
|
|
|
31
32
|
);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
if (
|
|
35
|
-
(args.
|
|
36
|
-
args.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
if (args.action === QUEST_ACTIONS.toggle) {
|
|
36
|
+
if (args.ids && args.ids.length > 1) {
|
|
37
|
+
return new Text(`${actionText} ${theme.fg("muted", `[${args.ids.length} tasks]`)}`, 0, 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const toggleId = args.id ?? args.ids?.[0];
|
|
41
|
+
if (toggleId !== undefined) {
|
|
42
|
+
return new Text(`${actionText} ${theme.fg("muted", `[${toggleId}]`)}`, 0, 0);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (args.action === QUEST_ACTIONS.delete) {
|
|
47
|
+
if (args.ids && args.ids.length > 1) {
|
|
48
|
+
return new Text(`${actionText} ${theme.fg("muted", `[${args.ids.length} tasks]`)}`, 0, 0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const deleteId = args.id ?? args.ids?.[0];
|
|
52
|
+
if (deleteId !== undefined) {
|
|
53
|
+
return new Text(`${actionText} ${theme.fg("muted", `[${deleteId}]`)}`, 0, 0);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (args.action === QUEST_ACTIONS.update && args.id !== undefined) {
|
|
40
58
|
return new Text(`${actionText} ${theme.fg("muted", `[${args.id}]`)}`, 0, 0);
|
|
41
59
|
}
|
|
42
60
|
|
|
@@ -59,8 +77,11 @@ export function renderQuestResult(config: ResolvedConfig) {
|
|
|
59
77
|
isPartial: options.isPartial,
|
|
60
78
|
});
|
|
61
79
|
const details = result.details as Record<string, unknown> | undefined;
|
|
62
|
-
const
|
|
63
|
-
const
|
|
80
|
+
const snapshotQuests = details?.snapshotQuests as Quest[] | undefined;
|
|
81
|
+
const allQuests = (snapshotQuests ?? details?.quests) as Quest[] | undefined;
|
|
82
|
+
const questsToRender = (snapshotQuests ?? details?.displayQuests ?? details?.quests) as
|
|
83
|
+
| Quest[]
|
|
84
|
+
| undefined;
|
|
64
85
|
|
|
65
86
|
if (
|
|
66
87
|
!Array.isArray(questsToRender) ||
|
package/src/tools/handler.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import type { AgentToolResult, ExtensionAPI } from "@
|
|
2
|
-
import { type Static, Type } from "
|
|
1
|
+
import type { AgentToolResult, ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type Static, Type } from "typebox";
|
|
3
3
|
import type { ResolvedConfig } from "../config.js";
|
|
4
4
|
import { logger } from "../logger.js";
|
|
5
5
|
import { QUEST_PROMPT_GATE, QUEST_PROMPT_REMINDER } from "../prompts.js";
|
|
6
6
|
import { makeToolResult, type QuestAction, type QuestLog } from "../quest/dataplane.js";
|
|
7
|
-
import { QUEST_ACTIONS } from "../quest/types.js";
|
|
7
|
+
import { QUEST_ACTIONS, type QuestActionType } from "../quest/types.js";
|
|
8
8
|
|
|
9
9
|
const SPLIT_DISPLAY_ACTIONS = [
|
|
10
10
|
QUEST_ACTIONS.add,
|
|
11
11
|
QUEST_ACTIONS.list,
|
|
12
12
|
QUEST_ACTIONS.split,
|
|
13
13
|
QUEST_ACTIONS.add_step,
|
|
14
|
-
QUEST_ACTIONS.
|
|
14
|
+
QUEST_ACTIONS.undo,
|
|
15
|
+
QUEST_ACTIONS.redo,
|
|
15
16
|
] as const;
|
|
16
17
|
|
|
17
18
|
import { invalidateQuestListWidget } from "../renderers/commands.js";
|
|
@@ -20,75 +21,72 @@ import { createQuestParams, type QuestParamsType } from "./params.js";
|
|
|
20
21
|
|
|
21
22
|
type QuestToolParams = Static<QuestParamsType>;
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
[QUEST_ACTIONS.add](questLog, params, toolCallId)
|
|
31
|
-
|
|
24
|
+
type ToolHandler = (
|
|
25
|
+
questLog: QuestLog,
|
|
26
|
+
params: QuestToolParams,
|
|
27
|
+
toolCallId: string,
|
|
28
|
+
) => AgentToolResult<unknown>;
|
|
29
|
+
|
|
30
|
+
const toolHandlers: Record<QuestActionType, ToolHandler> = {
|
|
31
|
+
[QUEST_ACTIONS.add]: (questLog, params, toolCallId) =>
|
|
32
|
+
runTool(questLog, toolCallId, {
|
|
32
33
|
type: QUEST_ACTIONS.add,
|
|
33
34
|
descriptions: params.descriptions,
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return runTool(questLog, toolCallId, {
|
|
35
|
+
}),
|
|
36
|
+
[QUEST_ACTIONS.split]: (questLog, params, toolCallId) =>
|
|
37
|
+
runTool(questLog, toolCallId, {
|
|
38
38
|
type: QUEST_ACTIONS.split,
|
|
39
39
|
id: params.id,
|
|
40
40
|
descriptions: params.descriptions,
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return runTool(questLog, toolCallId, {
|
|
41
|
+
}),
|
|
42
|
+
[QUEST_ACTIONS.add_step]: (questLog, params, toolCallId) =>
|
|
43
|
+
runTool(questLog, toolCallId, {
|
|
45
44
|
type: QUEST_ACTIONS.add_step,
|
|
46
45
|
id: params.id,
|
|
47
46
|
descriptions: params.descriptions,
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
}),
|
|
48
|
+
[QUEST_ACTIONS.list]: (questLog, _params, toolCallId) =>
|
|
49
|
+
runTool(questLog, toolCallId, { type: QUEST_ACTIONS.list }),
|
|
50
|
+
[QUEST_ACTIONS.toggle]: (questLog, params, toolCallId) =>
|
|
51
|
+
runTool(questLog, toolCallId, {
|
|
52
|
+
type: QUEST_ACTIONS.toggle,
|
|
53
|
+
id: params.id,
|
|
54
|
+
ids: params.ids,
|
|
55
|
+
}),
|
|
56
|
+
[QUEST_ACTIONS.update]: (questLog, params, toolCallId) =>
|
|
57
|
+
runTool(questLog, toolCallId, {
|
|
58
58
|
type: QUEST_ACTIONS.update,
|
|
59
59
|
id: params.id,
|
|
60
60
|
description: params.description,
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
}),
|
|
62
|
+
[QUEST_ACTIONS.delete]: (questLog, params, toolCallId) =>
|
|
63
|
+
runTool(questLog, toolCallId, {
|
|
64
|
+
type: QUEST_ACTIONS.delete,
|
|
65
|
+
id: params.id,
|
|
66
|
+
ids: params.ids,
|
|
67
|
+
}),
|
|
68
|
+
[QUEST_ACTIONS.clear]: (questLog, params, toolCallId) =>
|
|
69
|
+
runTool(questLog, toolCallId, { type: QUEST_ACTIONS.clear, all: params.all }),
|
|
70
|
+
[QUEST_ACTIONS.reorder]: (questLog, params, toolCallId) =>
|
|
71
|
+
runTool(questLog, toolCallId, {
|
|
71
72
|
type: QUEST_ACTIONS.reorder,
|
|
72
73
|
id: params.id,
|
|
73
74
|
targetId: params.targetId,
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return runTool(questLog, toolCallId, {
|
|
75
|
+
}),
|
|
76
|
+
[QUEST_ACTIONS.reparent]: (questLog, params, toolCallId) =>
|
|
77
|
+
runTool(questLog, toolCallId, {
|
|
78
78
|
type: QUEST_ACTIONS.reparent,
|
|
79
79
|
id: params.id,
|
|
80
80
|
parentId: params.parentId,
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
[QUEST_ACTIONS.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return runTool(questLog, toolCallId, { type: QUEST_ACTIONS.revert });
|
|
91
|
-
},
|
|
81
|
+
}),
|
|
82
|
+
[QUEST_ACTIONS.rules]: (questLog, _params, toolCallId) =>
|
|
83
|
+
runTool(questLog, toolCallId, { type: QUEST_ACTIONS.rules }),
|
|
84
|
+
[QUEST_ACTIONS.skill]: (questLog, _params, toolCallId) =>
|
|
85
|
+
runTool(questLog, toolCallId, { type: QUEST_ACTIONS.skill }),
|
|
86
|
+
[QUEST_ACTIONS.undo]: (questLog, _params, toolCallId) =>
|
|
87
|
+
runTool(questLog, toolCallId, { type: QUEST_ACTIONS.undo }),
|
|
88
|
+
[QUEST_ACTIONS.redo]: (questLog, _params, toolCallId) =>
|
|
89
|
+
runTool(questLog, toolCallId, { type: QUEST_ACTIONS.redo }),
|
|
92
90
|
};
|
|
93
91
|
|
|
94
92
|
function runTool(
|
|
@@ -104,11 +102,10 @@ function runTool(
|
|
|
104
102
|
action.type as (typeof SPLIT_DISPLAY_ACTIONS)[number],
|
|
105
103
|
)
|
|
106
104
|
? questLog.getAll()
|
|
107
|
-
: result.quest
|
|
108
|
-
|
|
109
|
-
: [];
|
|
105
|
+
: (result.quests ?? (result.quest ? [result.quest] : []));
|
|
106
|
+
const snapshotQuests = action.type === QUEST_ACTIONS.delete ? displayQuests : undefined;
|
|
110
107
|
|
|
111
|
-
return makeToolResult(result.message, questLog, displayQuests);
|
|
108
|
+
return makeToolResult(result.message, questLog, displayQuests, snapshotQuests);
|
|
112
109
|
}
|
|
113
110
|
|
|
114
111
|
export async function questToolExecute(
|
|
@@ -116,8 +113,13 @@ export async function questToolExecute(
|
|
|
116
113
|
toolCallId: string,
|
|
117
114
|
params: QuestToolParams,
|
|
118
115
|
): Promise<AgentToolResult<unknown>> {
|
|
119
|
-
logger.debug("quests:tool", "execute", {
|
|
120
|
-
|
|
116
|
+
logger.debug("quests:tool", "execute", {
|
|
117
|
+
toolCallId,
|
|
118
|
+
action: params.action,
|
|
119
|
+
id: params.id,
|
|
120
|
+
ids: params.ids,
|
|
121
|
+
});
|
|
122
|
+
const handler = toolHandlers[params.action as QuestActionType];
|
|
121
123
|
return handler(questLog, params, toolCallId);
|
|
122
124
|
}
|
|
123
125
|
|
|
@@ -140,7 +142,11 @@ export function registerQuestTool(
|
|
|
140
142
|
parameters: createQuestParams(config.ids.length),
|
|
141
143
|
execute: (toolCallId, params, _signal, _onUpdate, _ctx) =>
|
|
142
144
|
questToolExecute(questLog, toolCallId, params),
|
|
143
|
-
renderCall: renderQuestCall
|
|
145
|
+
renderCall: renderQuestCall as unknown as (
|
|
146
|
+
args: object,
|
|
147
|
+
theme: Theme,
|
|
148
|
+
context: unknown,
|
|
149
|
+
) => ReturnType<typeof renderQuestCall>,
|
|
144
150
|
renderResult: renderQuestResult(config),
|
|
145
151
|
});
|
|
146
152
|
|
|
@@ -159,8 +165,12 @@ export function registerQuestTool(
|
|
|
159
165
|
),
|
|
160
166
|
execute: async (_toolCallId, _params, _signal, _onUpdate, _ctx) =>
|
|
161
167
|
questToolExecute(questLog, "learn_quests", { action: QUEST_ACTIONS.skill }),
|
|
162
|
-
renderCall: (_args, theme, context) =>
|
|
163
|
-
renderQuestCall({ action: QUEST_ACTIONS.skill }, theme, context)
|
|
168
|
+
renderCall: ((_args: object, theme: Theme, context: unknown) =>
|
|
169
|
+
renderQuestCall({ action: QUEST_ACTIONS.skill }, theme, context)) as unknown as (
|
|
170
|
+
args: object,
|
|
171
|
+
theme: Theme,
|
|
172
|
+
context: unknown,
|
|
173
|
+
) => ReturnType<typeof renderQuestCall>,
|
|
164
174
|
renderResult: renderQuestResult(config),
|
|
165
175
|
});
|
|
166
176
|
}
|
package/src/tools/params.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { StringEnum } from "@
|
|
2
|
-
import { Type } from "
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
3
|
import { QUEST_ACTION_VALUES } from "../quest/types.js";
|
|
4
4
|
|
|
5
5
|
export function createQuestParams(idLength: number) {
|
|
@@ -22,7 +22,13 @@ export function createQuestParams(idLength: number) {
|
|
|
22
22
|
id: Type.Optional(
|
|
23
23
|
Type.String({
|
|
24
24
|
pattern,
|
|
25
|
-
description: `Quest ID (required for
|
|
25
|
+
description: `Quest ID (required for update, reorder, and reparent actions. For toggle and delete, provide either id or ids. ALWAYS use the ${idLength}-digit hex ID shown in brackets, never the positional number.`,
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
28
|
+
ids: Type.Optional(
|
|
29
|
+
Type.Array(Type.String({ pattern }), {
|
|
30
|
+
minItems: 1,
|
|
31
|
+
description: `Quest IDs for batch toggle or delete actions. Use this when operating on more than one ${idLength}-digit hex quest ID at once.`,
|
|
26
32
|
}),
|
|
27
33
|
),
|
|
28
34
|
targetId: Type.Optional(
|