@fickydev/pigent 0.1.21 → 0.1.23
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 +15 -0
- package/TODO.md +4 -0
- package/drizzle/migrations/0007_session_skills.sql +1 -0
- package/drizzle/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/agents/AgentRunner.ts +51 -10
- package/src/agents/BotCommandHandler.ts +112 -0
- package/src/config/skillRefs.ts +56 -0
- package/src/daemon/AgentDaemon.ts +1 -0
- package/src/db/client.ts +1 -0
- package/src/db/repositories/SessionRepository.ts +12 -0
- package/src/db/schema.ts +1 -0
- package/src/pi/PiAgentRunner.ts +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.23 - 2026-05-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Made `task-management` a runtime default active skill so task tools are available even when existing user configs do not list the skill.
|
|
8
|
+
|
|
9
|
+
## 0.1.22 - 2026-05-18
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added `/skills` command to list all known skills with active/inactive flags and source labels for the current chat session.
|
|
14
|
+
- Added session-scoped dynamic skill loading with `/skills add <skill>`, `/skills remove <skill>`, and `/skills clear`.
|
|
15
|
+
- Added current skill inventory to each agent prompt so users can ask natural questions like "what skills do you have?".
|
|
16
|
+
- Added `agent_sessions.session_skills` persistence and migration.
|
|
17
|
+
|
|
3
18
|
## 0.1.21 - 2026-05-18
|
|
4
19
|
|
|
5
20
|
### Fixed
|
package/TODO.md
CHANGED
|
@@ -146,6 +146,9 @@
|
|
|
146
146
|
- [x] Make README more end-user facing
|
|
147
147
|
- [x] Confirm per-agent system prompt injection
|
|
148
148
|
- [x] Confirm per-agent skills loading
|
|
149
|
+
- [x] Add `/skills` command for current session skills with active/inactive flags
|
|
150
|
+
- [x] Add session-scoped dynamic skill loading via `/skills add/remove/clear`
|
|
151
|
+
- [x] Add skill inventory context so agents can answer natural-language skill questions
|
|
149
152
|
- [x] Confirm per-agent extensions loading
|
|
150
153
|
- [ ] Confirm per-agent custom tools loading
|
|
151
154
|
- [x] Prototype one prompt through Pi SDK
|
|
@@ -170,6 +173,7 @@
|
|
|
170
173
|
- [x] Add max task runs per hour rate limit
|
|
171
174
|
- [x] Add `/task status` or similar command
|
|
172
175
|
- [x] Add task management as agent skill + tools (natural language task CRUD)
|
|
176
|
+
- [x] Activate task-management by default for all sessions
|
|
173
177
|
- [x] Improve task-management prompt guidance for reminder/proactive-message refusal
|
|
174
178
|
|
|
175
179
|
## Policy And Safety
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE `agent_sessions` ADD `session_skills` text DEFAULT '[]' NOT NULL;
|
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { InboundMessage } from "../channels/types";
|
|
3
3
|
import type { LoadedAgentConfig } from "../config/schemas";
|
|
4
|
+
import { defaultActiveSkills, hasSkillRef, listBuiltInSkills, parseSessionSkills, skillDisplayName, uniqueSkillRefs } from "../config/skillRefs";
|
|
4
5
|
import type { Repositories } from "../db/repositories";
|
|
6
|
+
import type { AgentSessionRow } from "../db/schema";
|
|
5
7
|
import { logger } from "../logging/logger";
|
|
6
8
|
import { PiAgentRunner } from "../pi/PiAgentRunner";
|
|
7
9
|
import { createTaskTools, type TaskToolContext } from "../pi/tools/taskTools";
|
|
@@ -117,8 +119,8 @@ export class AgentRunner {
|
|
|
117
119
|
agent,
|
|
118
120
|
profile: this.registry.getProfile(agent.profile),
|
|
119
121
|
session,
|
|
120
|
-
prompt: this.composePrompt(input, chatInstructions),
|
|
121
|
-
customTools: this.buildCustomTools(agent, input.message),
|
|
122
|
+
prompt: this.composePrompt(input, session, chatInstructions),
|
|
123
|
+
customTools: this.buildCustomTools(agent, session, input.message),
|
|
122
124
|
});
|
|
123
125
|
|
|
124
126
|
if (session.piSessionId !== result.piSessionId || session.piSessionPath !== result.piSessionPath) {
|
|
@@ -145,10 +147,10 @@ export class AgentRunner {
|
|
|
145
147
|
}
|
|
146
148
|
}
|
|
147
149
|
|
|
148
|
-
private buildCustomTools(agent: LoadedAgentConfig, message: InboundMessage): ToolDefinition[] | undefined {
|
|
150
|
+
private buildCustomTools(agent: LoadedAgentConfig, session: AgentSessionRow, message: InboundMessage): ToolDefinition[] | undefined {
|
|
149
151
|
const tools: ToolDefinition[] = [];
|
|
150
152
|
|
|
151
|
-
if (this.hasSkill(agent, "task-management") && this.scheduler) {
|
|
153
|
+
if (this.hasSkill(agent, session, "task-management") && this.scheduler) {
|
|
152
154
|
const ctx: TaskToolContext = {
|
|
153
155
|
agentId: agent.id,
|
|
154
156
|
channel: message.channel,
|
|
@@ -173,18 +175,18 @@ export class AgentRunner {
|
|
|
173
175
|
return tools.length > 0 ? tools : undefined;
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
private hasSkill(agent: LoadedAgentConfig, skillName: string): boolean {
|
|
178
|
+
private hasSkill(agent: LoadedAgentConfig, session: AgentSessionRow, skillName: string): boolean {
|
|
177
179
|
const allSkills = [
|
|
180
|
+
...defaultActiveSkills(),
|
|
178
181
|
...(agent.profile ? (this.registry.getProfile(agent.profile)?.defaultSkills ?? []) : []),
|
|
179
182
|
...agent.skills,
|
|
183
|
+
...parseSessionSkills(session.sessionSkills),
|
|
180
184
|
];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return base === skillName || s === skillName;
|
|
184
|
-
});
|
|
185
|
+
|
|
186
|
+
return hasSkillRef(allSkills, skillName);
|
|
185
187
|
}
|
|
186
188
|
|
|
187
|
-
private composePrompt(input: AgentRunInput, chatInstructions: string): string {
|
|
189
|
+
private composePrompt(input: AgentRunInput, session: AgentSessionRow, chatInstructions: string): string {
|
|
188
190
|
const parts: string[] = [
|
|
189
191
|
`[Channel]\n${input.message.channel}`,
|
|
190
192
|
`[Chat]\n${input.message.chatId}`,
|
|
@@ -202,11 +204,50 @@ export class AgentRunner {
|
|
|
202
204
|
parts.push(`[Chat Instructions]\n${chatInstructions}`);
|
|
203
205
|
}
|
|
204
206
|
|
|
207
|
+
const skillsContext = this.skillsContext(input.agentId, session);
|
|
208
|
+
if (skillsContext) {
|
|
209
|
+
parts.push(`[Skills]\n${skillsContext}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
205
212
|
parts.push(`[Message]\n${input.text}`);
|
|
206
213
|
|
|
207
214
|
return parts.join("\n\n");
|
|
208
215
|
}
|
|
209
216
|
|
|
217
|
+
private skillsContext(agentId: string, session: AgentSessionRow): string {
|
|
218
|
+
const agent = this.registry.getAgent(agentId);
|
|
219
|
+
if (!agent) return "";
|
|
220
|
+
|
|
221
|
+
const profile = this.registry.getProfile(agent.profile);
|
|
222
|
+
const defaultSkills = defaultActiveSkills();
|
|
223
|
+
const profileSkills = profile?.defaultSkills ?? [];
|
|
224
|
+
const agentSkills = agent.skills;
|
|
225
|
+
const sessionSkills = parseSessionSkills(session.sessionSkills);
|
|
226
|
+
const builtInSkills = listBuiltInSkills(import.meta.dir);
|
|
227
|
+
const activeSkills = uniqueSkillRefs([...defaultSkills, ...profileSkills, ...agentSkills, ...sessionSkills]);
|
|
228
|
+
const knownSkills = uniqueSkillRefs([...builtInSkills, ...activeSkills]).sort((a, b) =>
|
|
229
|
+
skillDisplayName(a).localeCompare(skillDisplayName(b)),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (knownSkills.length === 0) return "No skills are configured or available.";
|
|
233
|
+
|
|
234
|
+
return knownSkills
|
|
235
|
+
.map((skill) => {
|
|
236
|
+
const name = skillDisplayName(skill);
|
|
237
|
+
const active = hasSkillRef(activeSkills, name);
|
|
238
|
+
const sources = [];
|
|
239
|
+
|
|
240
|
+
if (hasSkillRef(defaultSkills, name)) sources.push("default");
|
|
241
|
+
if (hasSkillRef(profileSkills, name)) sources.push("profile");
|
|
242
|
+
if (hasSkillRef(agentSkills, name)) sources.push("agent");
|
|
243
|
+
if (hasSkillRef(sessionSkills, name)) sources.push("session");
|
|
244
|
+
if (hasSkillRef(builtInSkills, name)) sources.push("built-in");
|
|
245
|
+
|
|
246
|
+
return `- ${name}: ${active ? "active" : "available, inactive"}${sources.length > 0 ? ` (${sources.join(", ")})` : ""}`;
|
|
247
|
+
})
|
|
248
|
+
.join("\n");
|
|
249
|
+
}
|
|
250
|
+
|
|
210
251
|
private async chatInstructions(input: AgentRunInput): Promise<string> {
|
|
211
252
|
if (input.message.channel !== "telegram") return "";
|
|
212
253
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { InboundMessage, InlineKeyboardButton } from "../channels/types";
|
|
3
3
|
import type { LoadedAgentConfig, LoadedProfileConfig, ModelChoiceConfig } from "../config/schemas";
|
|
4
|
+
import { defaultActiveSkills, hasSkillRef, listBuiltInSkills, parseSessionSkills, skillDisplayName, uniqueSkillRefs } from "../config/skillRefs";
|
|
4
5
|
import type { AgentSessionRow } from "../db/schema";
|
|
5
6
|
import type { Repositories } from "../db/repositories";
|
|
6
7
|
import { PiAgentRunner, type PiContextUsage } from "../pi/PiAgentRunner";
|
|
@@ -47,6 +48,8 @@ export class BotCommandHandler {
|
|
|
47
48
|
return { handled: true, text: await this.newSessionText(message) };
|
|
48
49
|
case "/status":
|
|
49
50
|
return { handled: true, text: await this.statusText(message) };
|
|
51
|
+
case "/skills":
|
|
52
|
+
return { handled: true, text: await this.skillsText(message) };
|
|
50
53
|
case "/model":
|
|
51
54
|
return await this.modelResult(message);
|
|
52
55
|
case "/thinking":
|
|
@@ -71,6 +74,9 @@ export class BotCommandHandler {
|
|
|
71
74
|
"/agents - list agents for this chat",
|
|
72
75
|
"/new - start a fresh session for this chat",
|
|
73
76
|
"/status - show current session status",
|
|
77
|
+
"/skills - list skills for this chat session",
|
|
78
|
+
"/skills add <skill> - load a skill for this session",
|
|
79
|
+
"/skills remove <skill> - unload a session-loaded skill",
|
|
74
80
|
"/model - show model picker for this chat session",
|
|
75
81
|
"/model <provider/modelId> - set model for this chat session",
|
|
76
82
|
"/model default - clear model override for this chat session",
|
|
@@ -191,6 +197,84 @@ export class BotCommandHandler {
|
|
|
191
197
|
};
|
|
192
198
|
}
|
|
193
199
|
|
|
200
|
+
private async skillsText(message: InboundMessage): Promise<string> {
|
|
201
|
+
const sessionResult = await this.getDefaultSession(message);
|
|
202
|
+
if (!sessionResult.ok) return sessionResult.message;
|
|
203
|
+
|
|
204
|
+
const [, actionRaw, ...args] = message.text.trim().split(/\s+/);
|
|
205
|
+
const action = actionRaw?.toLowerCase() ?? "list";
|
|
206
|
+
const skillRef = args.join(" ").trim();
|
|
207
|
+
|
|
208
|
+
if (action === "add") {
|
|
209
|
+
if (!skillRef) return "Usage: /skills add <skill>";
|
|
210
|
+
|
|
211
|
+
const current = parseSessionSkills(sessionResult.session.sessionSkills);
|
|
212
|
+
const next = uniqueSkillRefs([...current, skillRef]);
|
|
213
|
+
await this.repositories.sessions.updateSessionSkills(sessionResult.session.id, next);
|
|
214
|
+
|
|
215
|
+
return [
|
|
216
|
+
"Skill loaded for current session: " + skillRef,
|
|
217
|
+
"",
|
|
218
|
+
"It will be included on the next agent response in this chat session.",
|
|
219
|
+
"Use /skills to see active skills.",
|
|
220
|
+
].join("\n");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (action === "remove") {
|
|
224
|
+
if (!skillRef) return "Usage: /skills remove <skill>";
|
|
225
|
+
|
|
226
|
+
const current = parseSessionSkills(sessionResult.session.sessionSkills);
|
|
227
|
+
const next = current.filter((item) => item !== skillRef);
|
|
228
|
+
await this.repositories.sessions.updateSessionSkills(sessionResult.session.id, next);
|
|
229
|
+
|
|
230
|
+
return next.length === current.length
|
|
231
|
+
? "Session skill not found: " + skillRef
|
|
232
|
+
: "Skill removed from current session: " + skillRef;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (action === "clear") {
|
|
236
|
+
await this.repositories.sessions.updateSessionSkills(sessionResult.session.id, []);
|
|
237
|
+
return "Session-loaded skills cleared.";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (action !== "list") {
|
|
241
|
+
return [
|
|
242
|
+
"Usage:",
|
|
243
|
+
"/skills - list skills",
|
|
244
|
+
"/skills add <skill> - load skill for this session",
|
|
245
|
+
"/skills remove <skill> - unload session skill",
|
|
246
|
+
"/skills clear - clear session-loaded skills",
|
|
247
|
+
].join("\n");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const agent = sessionResult.agent;
|
|
251
|
+
const profile = this.registry.getProfile(agent.profile);
|
|
252
|
+
const defaultSkills = defaultActiveSkills();
|
|
253
|
+
const profileSkills = profile?.defaultSkills ?? [];
|
|
254
|
+
const agentSkills = agent.skills;
|
|
255
|
+
const sessionSkills = parseSessionSkills(sessionResult.session.sessionSkills);
|
|
256
|
+
const builtInSkills = listBuiltInSkills(import.meta.dir);
|
|
257
|
+
const activeSkills = uniqueSkillRefs([...defaultSkills, ...profileSkills, ...agentSkills, ...sessionSkills]);
|
|
258
|
+
const knownSkills = uniqueSkillRefs([...builtInSkills, ...activeSkills]).sort((a, b) =>
|
|
259
|
+
skillDisplayName(a).localeCompare(skillDisplayName(b)),
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
return [
|
|
263
|
+
"Skills for current session",
|
|
264
|
+
"",
|
|
265
|
+
"Agent: " + agent.id,
|
|
266
|
+
"Session: " + sessionResult.session.id,
|
|
267
|
+
"",
|
|
268
|
+
"Available skills:",
|
|
269
|
+
...formatSkillRows(knownSkills, { defaultSkills, profileSkills, agentSkills, sessionSkills, builtInSkills }),
|
|
270
|
+
"",
|
|
271
|
+
"Legend: ✅ active, ⬜ available but inactive",
|
|
272
|
+
"Load on the fly: /skills add <skill>",
|
|
273
|
+
"Unload: /skills remove <skill>",
|
|
274
|
+
"Clear session-loaded: /skills clear",
|
|
275
|
+
].join("\n");
|
|
276
|
+
}
|
|
277
|
+
|
|
194
278
|
private async modelResult(message: InboundMessage): Promise<BotCommandResult> {
|
|
195
279
|
const sessionResult = await this.getDefaultSession(message);
|
|
196
280
|
if (!sessionResult.ok) return { handled: true, text: sessionResult.message };
|
|
@@ -511,6 +595,34 @@ function formatDuration(ms: number): string {
|
|
|
511
595
|
return Math.floor(hours / 24) + "d " + (hours % 24) + "h";
|
|
512
596
|
}
|
|
513
597
|
|
|
598
|
+
function formatSkillRows(
|
|
599
|
+
skills: string[],
|
|
600
|
+
sources: { defaultSkills: string[]; profileSkills: string[]; agentSkills: string[]; sessionSkills: string[]; builtInSkills: string[] },
|
|
601
|
+
): string[] {
|
|
602
|
+
if (skills.length === 0) return ["none"];
|
|
603
|
+
|
|
604
|
+
return skills.map((skill) => {
|
|
605
|
+
const name = skillDisplayName(skill);
|
|
606
|
+
const sourceLabels = [];
|
|
607
|
+
|
|
608
|
+
if (hasSkillRef(sources.defaultSkills, name)) sourceLabels.push("default");
|
|
609
|
+
if (hasSkillRef(sources.profileSkills, name)) sourceLabels.push("profile");
|
|
610
|
+
if (hasSkillRef(sources.agentSkills, name)) sourceLabels.push("agent");
|
|
611
|
+
if (hasSkillRef(sources.sessionSkills, name)) sourceLabels.push("session");
|
|
612
|
+
if (hasSkillRef(sources.builtInSkills, name)) sourceLabels.push("built-in");
|
|
613
|
+
|
|
614
|
+
const active = sourceLabels.some((source) => source !== "built-in");
|
|
615
|
+
const marker = active ? "✅" : "⬜";
|
|
616
|
+
const sourceText = sourceLabels.length > 0 ? sourceLabels.join(", ") : "configured";
|
|
617
|
+
|
|
618
|
+
return `${marker} ${name} (${active ? "active" : "inactive"}; ${sourceText})`;
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function formatList(items: string[]): string {
|
|
623
|
+
return items.length > 0 ? items.join(", ") : "none";
|
|
624
|
+
}
|
|
625
|
+
|
|
514
626
|
function formatPath(path: string): string {
|
|
515
627
|
const home = process.env.HOME || process.env.USERPROFILE;
|
|
516
628
|
if (home && path.startsWith(home)) {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_ACTIVE_SKILLS = ["task-management"] as const;
|
|
5
|
+
|
|
6
|
+
export function defaultActiveSkills(): string[] {
|
|
7
|
+
return [...DEFAULT_ACTIVE_SKILLS];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parseSessionSkills(raw: string | null | undefined): string[] {
|
|
11
|
+
if (!raw) return [];
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(raw);
|
|
15
|
+
if (!Array.isArray(parsed)) return [];
|
|
16
|
+
|
|
17
|
+
return parsed.filter((item): item is string => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
|
|
18
|
+
} catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function skillDisplayName(ref: string): string {
|
|
24
|
+
const normalized = ref.replace(/\\/g, "/").replace(/\/$/, "");
|
|
25
|
+
return normalized.split("/").pop() || ref;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function hasSkillRef(refs: string[], skillName: string): boolean {
|
|
29
|
+
return refs.some((ref) => ref === skillName || skillDisplayName(ref) === skillName);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function uniqueSkillRefs(refs: string[]): string[] {
|
|
33
|
+
const seen = new Set<string>();
|
|
34
|
+
const unique: string[] = [];
|
|
35
|
+
|
|
36
|
+
for (const ref of refs.map((item) => item.trim()).filter(Boolean)) {
|
|
37
|
+
if (seen.has(ref)) continue;
|
|
38
|
+
seen.add(ref);
|
|
39
|
+
unique.push(ref);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return unique;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function listBuiltInSkills(fromDir: string): string[] {
|
|
46
|
+
const skillsDir = resolve(fromDir, "../../skills");
|
|
47
|
+
|
|
48
|
+
if (!existsSync(skillsDir)) return [];
|
|
49
|
+
|
|
50
|
+
return readdirSync(skillsDir)
|
|
51
|
+
.filter((name) => {
|
|
52
|
+
const path = resolve(skillsDir, name);
|
|
53
|
+
return statSync(path).isDirectory() && existsSync(resolve(path, "SKILL.md"));
|
|
54
|
+
})
|
|
55
|
+
.sort();
|
|
56
|
+
}
|
|
@@ -181,6 +181,7 @@ function telegramCommands() {
|
|
|
181
181
|
{ command: "agents", description: "List agents available in this chat" },
|
|
182
182
|
{ command: "new", description: "Start a fresh chat session" },
|
|
183
183
|
{ command: "status", description: "Show current session status" },
|
|
184
|
+
{ command: "skills", description: "List or load skills for this session" },
|
|
184
185
|
{ command: "model", description: "Choose model for this chat session" },
|
|
185
186
|
{ command: "thinking", description: "Choose thinking level for this chat session" },
|
|
186
187
|
{ command: "agent", description: "Send message to a specific agent" },
|
package/src/db/client.ts
CHANGED
|
@@ -30,6 +30,7 @@ function ensureRuntimeSchema(): void {
|
|
|
30
30
|
ensureColumn("agent_sessions", "active", "ALTER TABLE `agent_sessions` ADD `active` integer DEFAULT true NOT NULL");
|
|
31
31
|
ensureColumn("agent_sessions", "ended_at", "ALTER TABLE `agent_sessions` ADD `ended_at` integer");
|
|
32
32
|
ensureColumn("agent_sessions", "pi_session_path", "ALTER TABLE `agent_sessions` ADD `pi_session_path` text");
|
|
33
|
+
ensureColumn("agent_sessions", "session_skills", "ALTER TABLE `agent_sessions` ADD `session_skills` text DEFAULT '[]' NOT NULL");
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
function ensureColumn(table: string, column: string, statement: string): void {
|
|
@@ -75,6 +75,18 @@ export class SessionRepository {
|
|
|
75
75
|
return this.requireById(id);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
async updateSessionSkills(id: string, skills: string[]): Promise<AgentSessionRow> {
|
|
79
|
+
await this.db
|
|
80
|
+
.update(agentSessions)
|
|
81
|
+
.set({
|
|
82
|
+
sessionSkills: JSON.stringify(skills),
|
|
83
|
+
updatedAt: Date.now(),
|
|
84
|
+
})
|
|
85
|
+
.where(eq(agentSessions.id, id));
|
|
86
|
+
|
|
87
|
+
return this.requireById(id);
|
|
88
|
+
}
|
|
89
|
+
|
|
78
90
|
async findById(id: string): Promise<AgentSessionRow | null> {
|
|
79
91
|
const row = await this.db.query.agentSessions.findFirst({
|
|
80
92
|
where: eq(agentSessions.id, id),
|
package/src/db/schema.ts
CHANGED
|
@@ -51,6 +51,7 @@ export const agentSessions = sqliteTable(
|
|
|
51
51
|
instructionsHash: text("instructions_hash"),
|
|
52
52
|
model: text("model"),
|
|
53
53
|
thinkingLevel: text("thinking_level", { enum: ["off", "low", "medium", "high"] }),
|
|
54
|
+
sessionSkills: text("session_skills").notNull().default("[]"),
|
|
54
55
|
active: integer("active", { mode: "boolean" }).notNull().default(true),
|
|
55
56
|
endedAt: integer("ended_at"),
|
|
56
57
|
createdAt: integer("created_at").notNull(),
|
package/src/pi/PiAgentRunner.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { mkdir } from "node:fs/promises";
|
|
11
11
|
import { resolve } from "node:path";
|
|
12
12
|
import type { LoadedAgentConfig, LoadedProfileConfig } from "../config/schemas";
|
|
13
|
+
import { defaultActiveSkills, parseSessionSkills } from "../config/skillRefs";
|
|
13
14
|
import type { AgentSessionRow } from "../db/schema";
|
|
14
15
|
import { resolveModelSelection } from "./PiModelResolver";
|
|
15
16
|
import { loadOrCreatePiSession } from "./PiSessionFactory";
|
|
@@ -82,7 +83,7 @@ export class PiAgentRunner {
|
|
|
82
83
|
compaction: { enabled: false },
|
|
83
84
|
});
|
|
84
85
|
const agentDir = getAgentDir();
|
|
85
|
-
const skillPaths = resolveSkillPaths(input.agent, input.profile);
|
|
86
|
+
const skillPaths = resolveSkillPaths(input.agent, input.profile, input.session);
|
|
86
87
|
const resourceLoader = new DefaultResourceLoader({
|
|
87
88
|
cwd: workspace,
|
|
88
89
|
agentDir,
|
|
@@ -114,9 +115,13 @@ export class PiAgentRunner {
|
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
function resolveSkillPaths(agent: LoadedAgentConfig, profile: LoadedProfileConfig | null): string[] {
|
|
118
|
+
function resolveSkillPaths(agent: LoadedAgentConfig, profile: LoadedProfileConfig | null, session: AgentSessionRow): string[] {
|
|
118
119
|
const paths: string[] = [];
|
|
119
120
|
|
|
121
|
+
for (const s of defaultActiveSkills()) {
|
|
122
|
+
paths.push(resolveSkillPath(s, agent.baseDir));
|
|
123
|
+
}
|
|
124
|
+
|
|
120
125
|
if (profile) {
|
|
121
126
|
for (const s of profile.defaultSkills) {
|
|
122
127
|
paths.push(resolveSkillPath(s, profile.baseDir));
|
|
@@ -127,6 +132,10 @@ function resolveSkillPaths(agent: LoadedAgentConfig, profile: LoadedProfileConfi
|
|
|
127
132
|
paths.push(resolveSkillPath(s, agent.baseDir));
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
for (const s of parseSessionSkills(session.sessionSkills)) {
|
|
136
|
+
paths.push(resolveSkillPath(s, agent.baseDir));
|
|
137
|
+
}
|
|
138
|
+
|
|
130
139
|
return paths;
|
|
131
140
|
}
|
|
132
141
|
|