@fickydev/pigent 0.1.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/.env.example +22 -0
- package/AGENTS.md +242 -0
- package/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/PLAN.md +369 -0
- package/README.md +369 -0
- package/TODO.md +183 -0
- package/agents/coder/SYSTEM.md +3 -0
- package/agents/coder/agent.yaml +20 -0
- package/drizzle/migrations/0000_great_daredevil.sql +78 -0
- package/drizzle/migrations/meta/0000_snapshot.json +505 -0
- package/drizzle/migrations/meta/_journal.json +13 -0
- package/drizzle.config.ts +13 -0
- package/package.json +66 -0
- package/pigent.yaml +12 -0
- package/profiles/software-engineer.yaml +11 -0
- package/src/agents/AgentRunner.ts +112 -0
- package/src/agents/BotCommandHandler.ts +65 -0
- package/src/agents/MessageRouter.ts +106 -0
- package/src/channels/telegram/TelegramApi.ts +67 -0
- package/src/channels/telegram/TelegramPollingAdapter.ts +123 -0
- package/src/channels/telegram/types.ts +50 -0
- package/src/channels/types.ts +29 -0
- package/src/cli/run.ts +77 -0
- package/src/cli/setup.ts +261 -0
- package/src/config/loadConfig.ts +115 -0
- package/src/config/schemas.ts +92 -0
- package/src/daemon/AgentDaemon.ts +161 -0
- package/src/db/client.ts +23 -0
- package/src/db/repositories/AgentRepository.ts +45 -0
- package/src/db/repositories/MessageRepository.ts +45 -0
- package/src/db/repositories/RuntimeKvRepository.ts +27 -0
- package/src/db/repositories/SessionRepository.ts +65 -0
- package/src/db/repositories/TelegramRepository.ts +98 -0
- package/src/db/repositories/index.ts +18 -0
- package/src/db/schema.ts +106 -0
- package/src/logging/logger.ts +45 -0
- package/src/main.ts +37 -0
- package/src/pi/PiAgentRunner.ts +73 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { nanoid } from "nanoid";
|
|
2
|
+
import type { DbClient } from "../client";
|
|
3
|
+
import { messages, type MessageRow } from "../schema";
|
|
4
|
+
|
|
5
|
+
export type CreateMessageInput = {
|
|
6
|
+
agentId: string;
|
|
7
|
+
sessionId?: string | null;
|
|
8
|
+
channel: string;
|
|
9
|
+
direction: "inbound" | "outbound" | "internal";
|
|
10
|
+
senderId?: string | null;
|
|
11
|
+
chatId?: string | null;
|
|
12
|
+
threadId?: string | null;
|
|
13
|
+
content: string;
|
|
14
|
+
rawJson?: unknown;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class MessageRepository {
|
|
18
|
+
constructor(private readonly db: DbClient) {}
|
|
19
|
+
|
|
20
|
+
async create(input: CreateMessageInput): Promise<MessageRow> {
|
|
21
|
+
const id = nanoid();
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
|
|
24
|
+
await this.db.insert(messages).values({
|
|
25
|
+
id,
|
|
26
|
+
agentId: input.agentId,
|
|
27
|
+
sessionId: input.sessionId ?? null,
|
|
28
|
+
channel: input.channel,
|
|
29
|
+
direction: input.direction,
|
|
30
|
+
senderId: input.senderId ?? null,
|
|
31
|
+
chatId: input.chatId ?? null,
|
|
32
|
+
threadId: input.threadId ?? null,
|
|
33
|
+
content: input.content,
|
|
34
|
+
rawJson: input.rawJson === undefined ? null : JSON.stringify(input.rawJson),
|
|
35
|
+
createdAt: now,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const row = await this.db.query.messages.findFirst({
|
|
39
|
+
where: (table, { eq }) => eq(table.id, id),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!row) throw new Error(`failed to create message ${id}`);
|
|
43
|
+
return row;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import type { DbClient } from "../client";
|
|
3
|
+
import { runtimeKv } from "../schema";
|
|
4
|
+
|
|
5
|
+
export class RuntimeKvRepository {
|
|
6
|
+
constructor(private readonly db: DbClient) {}
|
|
7
|
+
|
|
8
|
+
async get(key: string): Promise<string | null> {
|
|
9
|
+
const row = await this.db.query.runtimeKv.findFirst({
|
|
10
|
+
where: eq(runtimeKv.key, key),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return row?.value ?? null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async set(key: string, value: string): Promise<void> {
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
|
|
19
|
+
await this.db
|
|
20
|
+
.insert(runtimeKv)
|
|
21
|
+
.values({ key, value, updatedAt: now })
|
|
22
|
+
.onConflictDoUpdate({
|
|
23
|
+
target: runtimeKv.key,
|
|
24
|
+
set: { value, updatedAt: now },
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { and, eq, isNull } from "drizzle-orm";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import type { DbClient } from "../client";
|
|
4
|
+
import { agentSessions, type AgentSessionRow } from "../schema";
|
|
5
|
+
|
|
6
|
+
export type SessionKey = {
|
|
7
|
+
agentId: string;
|
|
8
|
+
channel: string;
|
|
9
|
+
chatId: string;
|
|
10
|
+
threadId?: string | null;
|
|
11
|
+
userId?: string | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export class SessionRepository {
|
|
15
|
+
constructor(private readonly db: DbClient) {}
|
|
16
|
+
|
|
17
|
+
async getOrCreate(key: SessionKey): Promise<AgentSessionRow> {
|
|
18
|
+
const existing = await this.findByKey(key);
|
|
19
|
+
if (existing) return existing;
|
|
20
|
+
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const id = nanoid();
|
|
23
|
+
|
|
24
|
+
await this.db.insert(agentSessions).values({
|
|
25
|
+
id,
|
|
26
|
+
agentId: key.agentId,
|
|
27
|
+
channel: key.channel,
|
|
28
|
+
chatId: key.chatId,
|
|
29
|
+
threadId: key.threadId ?? null,
|
|
30
|
+
userId: key.userId ?? null,
|
|
31
|
+
createdAt: now,
|
|
32
|
+
updatedAt: now,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const created = await this.findById(id);
|
|
36
|
+
if (!created) throw new Error(`failed to create session ${id}`);
|
|
37
|
+
|
|
38
|
+
return created;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async findById(id: string): Promise<AgentSessionRow | null> {
|
|
42
|
+
const row = await this.db.query.agentSessions.findFirst({
|
|
43
|
+
where: eq(agentSessions.id, id),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return row ?? null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async findByKey(key: SessionKey): Promise<AgentSessionRow | null> {
|
|
50
|
+
const threadPredicate = key.threadId
|
|
51
|
+
? eq(agentSessions.threadId, key.threadId)
|
|
52
|
+
: isNull(agentSessions.threadId);
|
|
53
|
+
|
|
54
|
+
const row = await this.db.query.agentSessions.findFirst({
|
|
55
|
+
where: and(
|
|
56
|
+
eq(agentSessions.agentId, key.agentId),
|
|
57
|
+
eq(agentSessions.channel, key.channel),
|
|
58
|
+
eq(agentSessions.chatId, key.chatId),
|
|
59
|
+
threadPredicate,
|
|
60
|
+
),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return row ?? null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { and, eq } from "drizzle-orm";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import type { TelegramChatConfig } from "../../config/schemas";
|
|
4
|
+
import type { InboundMessage } from "../../channels/types";
|
|
5
|
+
import type { DbClient } from "../client";
|
|
6
|
+
import { telegramChatAgents, telegramChats, type TelegramChatRow } from "../schema";
|
|
7
|
+
|
|
8
|
+
export class TelegramRepository {
|
|
9
|
+
constructor(private readonly db: DbClient) {}
|
|
10
|
+
|
|
11
|
+
async upsertConfiguredChat(chat: TelegramChatConfig): Promise<void> {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
|
|
14
|
+
await this.db
|
|
15
|
+
.insert(telegramChats)
|
|
16
|
+
.values({
|
|
17
|
+
chatId: chat.chatId,
|
|
18
|
+
title: chat.title,
|
|
19
|
+
defaultAgentId: chat.defaultAgent,
|
|
20
|
+
instructions: chat.instructions,
|
|
21
|
+
enabled: chat.enabled,
|
|
22
|
+
createdAt: now,
|
|
23
|
+
updatedAt: now,
|
|
24
|
+
})
|
|
25
|
+
.onConflictDoUpdate({
|
|
26
|
+
target: telegramChats.chatId,
|
|
27
|
+
set: {
|
|
28
|
+
title: chat.title,
|
|
29
|
+
defaultAgentId: chat.defaultAgent,
|
|
30
|
+
instructions: chat.instructions,
|
|
31
|
+
enabled: chat.enabled,
|
|
32
|
+
updatedAt: now,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const allowedAgents = chat.allowedAgents.length > 0 ? chat.allowedAgents : [chat.defaultAgent];
|
|
37
|
+
|
|
38
|
+
for (const agentId of allowedAgents) {
|
|
39
|
+
await this.db
|
|
40
|
+
.insert(telegramChatAgents)
|
|
41
|
+
.values({
|
|
42
|
+
id: nanoid(),
|
|
43
|
+
chatId: chat.chatId,
|
|
44
|
+
agentId,
|
|
45
|
+
enabled: true,
|
|
46
|
+
customInstructions: "",
|
|
47
|
+
createdAt: now,
|
|
48
|
+
updatedAt: now,
|
|
49
|
+
})
|
|
50
|
+
.onConflictDoUpdate({
|
|
51
|
+
target: [telegramChatAgents.chatId, telegramChatAgents.agentId],
|
|
52
|
+
set: {
|
|
53
|
+
enabled: true,
|
|
54
|
+
updatedAt: now,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async autoConfigureChat(message: InboundMessage, defaultAgentId: string): Promise<void> {
|
|
61
|
+
const existing = await this.findChat(message.chatId);
|
|
62
|
+
if (existing) return;
|
|
63
|
+
|
|
64
|
+
const raw = message.raw as { chat?: { title?: string; username?: string; first_name?: string; last_name?: string } } | null;
|
|
65
|
+
const title = raw?.chat
|
|
66
|
+
? raw.chat.title ?? [raw.chat.first_name, raw.chat.last_name].filter(Boolean).join(" ") ?? raw.chat.username
|
|
67
|
+
: undefined;
|
|
68
|
+
|
|
69
|
+
await this.upsertConfiguredChat({
|
|
70
|
+
chatId: message.chatId,
|
|
71
|
+
title: title || undefined,
|
|
72
|
+
defaultAgent: defaultAgentId,
|
|
73
|
+
allowedAgents: [defaultAgentId],
|
|
74
|
+
instructions: "",
|
|
75
|
+
enabled: true,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async findChat(chatId: string): Promise<TelegramChatRow | null> {
|
|
80
|
+
const row = await this.db.query.telegramChats.findFirst({
|
|
81
|
+
where: eq(telegramChats.chatId, chatId),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return row ?? null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async isAgentAllowed(chatId: string, agentId: string): Promise<boolean> {
|
|
88
|
+
const row = await this.db.query.telegramChatAgents.findFirst({
|
|
89
|
+
where: and(
|
|
90
|
+
eq(telegramChatAgents.chatId, chatId),
|
|
91
|
+
eq(telegramChatAgents.agentId, agentId),
|
|
92
|
+
eq(telegramChatAgents.enabled, true),
|
|
93
|
+
),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return Boolean(row);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { db } from "../client";
|
|
2
|
+
import { AgentRepository } from "./AgentRepository";
|
|
3
|
+
import { MessageRepository } from "./MessageRepository";
|
|
4
|
+
import { RuntimeKvRepository } from "./RuntimeKvRepository";
|
|
5
|
+
import { SessionRepository } from "./SessionRepository";
|
|
6
|
+
import { TelegramRepository } from "./TelegramRepository";
|
|
7
|
+
|
|
8
|
+
export type Repositories = ReturnType<typeof createRepositories>;
|
|
9
|
+
|
|
10
|
+
export function createRepositories(dbClient = db) {
|
|
11
|
+
return {
|
|
12
|
+
agents: new AgentRepository(dbClient),
|
|
13
|
+
messages: new MessageRepository(dbClient),
|
|
14
|
+
runtimeKv: new RuntimeKvRepository(dbClient),
|
|
15
|
+
sessions: new SessionRepository(dbClient),
|
|
16
|
+
telegram: new TelegramRepository(dbClient),
|
|
17
|
+
};
|
|
18
|
+
}
|
package/src/db/schema.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
2
|
+
|
|
3
|
+
export const agents = sqliteTable("agents", {
|
|
4
|
+
id: text("id").primaryKey(),
|
|
5
|
+
name: text("name").notNull(),
|
|
6
|
+
profile: text("profile").notNull(),
|
|
7
|
+
workspace: text("workspace").notNull(),
|
|
8
|
+
configJson: text("config_json").notNull(),
|
|
9
|
+
systemPrompt: text("system_prompt").notNull().default(""),
|
|
10
|
+
createdAt: integer("created_at").notNull(),
|
|
11
|
+
updatedAt: integer("updated_at").notNull(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const telegramChats = sqliteTable("telegram_chats", {
|
|
15
|
+
chatId: text("chat_id").primaryKey(),
|
|
16
|
+
title: text("title"),
|
|
17
|
+
defaultAgentId: text("default_agent_id"),
|
|
18
|
+
instructions: text("instructions").notNull().default(""),
|
|
19
|
+
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
|
20
|
+
createdAt: integer("created_at").notNull(),
|
|
21
|
+
updatedAt: integer("updated_at").notNull(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const telegramChatAgents = sqliteTable(
|
|
25
|
+
"telegram_chat_agents",
|
|
26
|
+
{
|
|
27
|
+
id: text("id").primaryKey(),
|
|
28
|
+
chatId: text("chat_id").notNull(),
|
|
29
|
+
agentId: text("agent_id").notNull(),
|
|
30
|
+
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
|
31
|
+
customInstructions: text("custom_instructions").notNull().default(""),
|
|
32
|
+
createdAt: integer("created_at").notNull(),
|
|
33
|
+
updatedAt: integer("updated_at").notNull(),
|
|
34
|
+
},
|
|
35
|
+
(table) => ({
|
|
36
|
+
chatAgentUnique: uniqueIndex("telegram_chat_agents_chat_agent_unique").on(table.chatId, table.agentId),
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const agentSessions = sqliteTable(
|
|
41
|
+
"agent_sessions",
|
|
42
|
+
{
|
|
43
|
+
id: text("id").primaryKey(),
|
|
44
|
+
agentId: text("agent_id").notNull(),
|
|
45
|
+
channel: text("channel").notNull(),
|
|
46
|
+
chatId: text("chat_id").notNull(),
|
|
47
|
+
threadId: text("thread_id"),
|
|
48
|
+
userId: text("user_id"),
|
|
49
|
+
piSessionId: text("pi_session_id"),
|
|
50
|
+
instructionsHash: text("instructions_hash"),
|
|
51
|
+
createdAt: integer("created_at").notNull(),
|
|
52
|
+
updatedAt: integer("updated_at").notNull(),
|
|
53
|
+
},
|
|
54
|
+
(table) => ({
|
|
55
|
+
sessionKeyUnique: uniqueIndex("agent_sessions_key_unique").on(
|
|
56
|
+
table.agentId,
|
|
57
|
+
table.channel,
|
|
58
|
+
table.chatId,
|
|
59
|
+
table.threadId,
|
|
60
|
+
),
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
export const messages = sqliteTable("messages", {
|
|
65
|
+
id: text("id").primaryKey(),
|
|
66
|
+
agentId: text("agent_id").notNull(),
|
|
67
|
+
sessionId: text("session_id"),
|
|
68
|
+
channel: text("channel").notNull(),
|
|
69
|
+
direction: text("direction", { enum: ["inbound", "outbound", "internal"] }).notNull(),
|
|
70
|
+
senderId: text("sender_id"),
|
|
71
|
+
chatId: text("chat_id"),
|
|
72
|
+
threadId: text("thread_id"),
|
|
73
|
+
content: text("content").notNull(),
|
|
74
|
+
rawJson: text("raw_json"),
|
|
75
|
+
createdAt: integer("created_at").notNull(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const heartbeats = sqliteTable("heartbeats", {
|
|
79
|
+
id: text("id").primaryKey(),
|
|
80
|
+
agentId: text("agent_id").notNull(),
|
|
81
|
+
sessionId: text("session_id"),
|
|
82
|
+
status: text("status", { enum: ["pending", "running", "noop", "notified", "failed"] }).notNull(),
|
|
83
|
+
prompt: text("prompt").notNull(),
|
|
84
|
+
result: text("result"),
|
|
85
|
+
error: text("error"),
|
|
86
|
+
startedAt: integer("started_at"),
|
|
87
|
+
finishedAt: integer("finished_at"),
|
|
88
|
+
createdAt: integer("created_at").notNull(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const runtimeKv = sqliteTable("runtime_kv", {
|
|
92
|
+
key: text("key").primaryKey(),
|
|
93
|
+
value: text("value").notNull(),
|
|
94
|
+
updatedAt: integer("updated_at").notNull(),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export type AgentRow = typeof agents.$inferSelect;
|
|
98
|
+
export type NewAgentRow = typeof agents.$inferInsert;
|
|
99
|
+
export type TelegramChatRow = typeof telegramChats.$inferSelect;
|
|
100
|
+
export type NewTelegramChatRow = typeof telegramChats.$inferInsert;
|
|
101
|
+
export type AgentSessionRow = typeof agentSessions.$inferSelect;
|
|
102
|
+
export type NewAgentSessionRow = typeof agentSessions.$inferInsert;
|
|
103
|
+
export type MessageRow = typeof messages.$inferSelect;
|
|
104
|
+
export type NewMessageRow = typeof messages.$inferInsert;
|
|
105
|
+
export type HeartbeatRow = typeof heartbeats.$inferSelect;
|
|
106
|
+
export type NewHeartbeatRow = typeof heartbeats.$inferInsert;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
+
|
|
3
|
+
type LogFields = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
const levels: Record<LogLevel, number> = {
|
|
6
|
+
debug: 10,
|
|
7
|
+
info: 20,
|
|
8
|
+
warn: 30,
|
|
9
|
+
error: 40,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const configuredLevel = (process.env.LOG_LEVEL ?? "info") as LogLevel;
|
|
13
|
+
const minLevel = levels[configuredLevel] ?? levels.info;
|
|
14
|
+
|
|
15
|
+
function writeLog(level: LogLevel, message: string, fields?: LogFields): void {
|
|
16
|
+
if (levels[level] < minLevel) return;
|
|
17
|
+
|
|
18
|
+
const entry = {
|
|
19
|
+
time: new Date().toISOString(),
|
|
20
|
+
level,
|
|
21
|
+
message,
|
|
22
|
+
...fields,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const line = JSON.stringify(entry);
|
|
26
|
+
|
|
27
|
+
if (level === "error") {
|
|
28
|
+
console.error(line);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (level === "warn") {
|
|
33
|
+
console.warn(line);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(line);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const logger = {
|
|
41
|
+
debug: (message: string, fields?: LogFields) => writeLog("debug", message, fields),
|
|
42
|
+
info: (message: string, fields?: LogFields) => writeLog("info", message, fields),
|
|
43
|
+
warn: (message: string, fields?: LogFields) => writeLog("warn", message, fields),
|
|
44
|
+
error: (message: string, fields?: LogFields) => writeLog("error", message, fields),
|
|
45
|
+
};
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AgentDaemon } from "./daemon/AgentDaemon";
|
|
2
|
+
import { logger } from "./logging/logger";
|
|
3
|
+
|
|
4
|
+
const daemon = await AgentDaemon.create();
|
|
5
|
+
|
|
6
|
+
let stopping = false;
|
|
7
|
+
|
|
8
|
+
async function shutdown(signal: string): Promise<void> {
|
|
9
|
+
if (stopping) return;
|
|
10
|
+
stopping = true;
|
|
11
|
+
|
|
12
|
+
logger.info("shutdown requested", { signal });
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await daemon.stop();
|
|
16
|
+
process.exit(0);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
logger.error("shutdown failed", {
|
|
19
|
+
error: error instanceof Error ? error.message : String(error),
|
|
20
|
+
});
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
26
|
+
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
logger.info("pigent starting");
|
|
30
|
+
await daemon.start();
|
|
31
|
+
logger.info("pigent ready");
|
|
32
|
+
} catch (error) {
|
|
33
|
+
logger.error("pigent failed to start", {
|
|
34
|
+
error: error instanceof Error ? error.message : String(error),
|
|
35
|
+
});
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthStorage,
|
|
3
|
+
createAgentSession,
|
|
4
|
+
DefaultResourceLoader,
|
|
5
|
+
ModelRegistry,
|
|
6
|
+
getAgentDir,
|
|
7
|
+
SessionManager,
|
|
8
|
+
SettingsManager,
|
|
9
|
+
} from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { mkdir } from "node:fs/promises";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import type { LoadedAgentConfig, ProfileConfig } from "../config/schemas";
|
|
13
|
+
|
|
14
|
+
export type PiAgentRunInput = {
|
|
15
|
+
agent: LoadedAgentConfig;
|
|
16
|
+
profile: ProfileConfig | null;
|
|
17
|
+
prompt: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class PiAgentRunner {
|
|
21
|
+
async run(input: PiAgentRunInput): Promise<string> {
|
|
22
|
+
const workspace = resolve(input.agent.workspace);
|
|
23
|
+
await mkdir(workspace, { recursive: true });
|
|
24
|
+
|
|
25
|
+
const systemPrompt = composeSystemPrompt(input.agent, input.profile);
|
|
26
|
+
const settingsManager = SettingsManager.inMemory({
|
|
27
|
+
compaction: { enabled: false },
|
|
28
|
+
});
|
|
29
|
+
const agentDir = getAgentDir();
|
|
30
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
31
|
+
cwd: workspace,
|
|
32
|
+
agentDir,
|
|
33
|
+
settingsManager,
|
|
34
|
+
systemPromptOverride: () => systemPrompt,
|
|
35
|
+
});
|
|
36
|
+
await resourceLoader.reload();
|
|
37
|
+
|
|
38
|
+
const authStorage = AuthStorage.create();
|
|
39
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
40
|
+
const { session } = await createAgentSession({
|
|
41
|
+
cwd: workspace,
|
|
42
|
+
authStorage,
|
|
43
|
+
modelRegistry,
|
|
44
|
+
resourceLoader,
|
|
45
|
+
sessionManager: SessionManager.create(workspace),
|
|
46
|
+
settingsManager,
|
|
47
|
+
agentDir,
|
|
48
|
+
tools: [],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
let response = "";
|
|
52
|
+
session.subscribe((event) => {
|
|
53
|
+
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
54
|
+
response += event.assistantMessageEvent.delta;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await session.prompt(input.prompt);
|
|
59
|
+
|
|
60
|
+
return response.trim() || "(no response)";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function composeSystemPrompt(agent: LoadedAgentConfig, profile: ProfileConfig | null): string {
|
|
65
|
+
return [
|
|
66
|
+
profile?.instructions,
|
|
67
|
+
agent.systemPrompt,
|
|
68
|
+
"You are running inside Pigent, a channel-orchestrated autonomous agent daemon.",
|
|
69
|
+
"Do not reveal secrets. Keep responses suitable for external chat.",
|
|
70
|
+
]
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.join("\n\n");
|
|
73
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"types": ["bun-types"],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"resolveJsonModule": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*.ts", "drizzle.config.ts"]
|
|
17
|
+
}
|