pragma-so 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.
Files changed (65) hide show
  1. package/cli/index.ts +882 -0
  2. package/index.ts +3 -0
  3. package/package.json +53 -0
  4. package/server/connectorBinaries.ts +103 -0
  5. package/server/connectorRegistry.ts +158 -0
  6. package/server/conversation/adapterRegistry.ts +53 -0
  7. package/server/conversation/adapters/claudeAdapter.ts +138 -0
  8. package/server/conversation/adapters/codexAdapter.ts +142 -0
  9. package/server/conversation/adapters.ts +224 -0
  10. package/server/conversation/executeRunner.ts +1191 -0
  11. package/server/conversation/gitWorkflow.ts +1037 -0
  12. package/server/conversation/models.ts +23 -0
  13. package/server/conversation/pragmaCli.ts +34 -0
  14. package/server/conversation/prompts.ts +335 -0
  15. package/server/conversation/store.ts +805 -0
  16. package/server/conversation/titleGenerator.ts +106 -0
  17. package/server/conversation/turnRunner.ts +365 -0
  18. package/server/conversation/types.ts +134 -0
  19. package/server/db.ts +837 -0
  20. package/server/http/middleware.ts +31 -0
  21. package/server/http/schemas.ts +430 -0
  22. package/server/http/validators.ts +38 -0
  23. package/server/index.ts +6560 -0
  24. package/server/process/runCommand.ts +142 -0
  25. package/server/stores/agentStore.ts +167 -0
  26. package/server/stores/connectorStore.ts +299 -0
  27. package/server/stores/humanStore.ts +28 -0
  28. package/server/stores/skillStore.ts +127 -0
  29. package/server/stores/taskStore.ts +371 -0
  30. package/shared/net.ts +24 -0
  31. package/tsconfig.json +14 -0
  32. package/ui/index.html +14 -0
  33. package/ui/public/favicon-32.png +0 -0
  34. package/ui/public/favicon.png +0 -0
  35. package/ui/src/App.jsx +1338 -0
  36. package/ui/src/api.js +954 -0
  37. package/ui/src/components/CodeView.jsx +319 -0
  38. package/ui/src/components/ConnectionsView.jsx +1004 -0
  39. package/ui/src/components/ContextView.jsx +315 -0
  40. package/ui/src/components/ConversationDrawer.jsx +963 -0
  41. package/ui/src/components/EmptyPane.jsx +20 -0
  42. package/ui/src/components/FeedView.jsx +773 -0
  43. package/ui/src/components/FilesView.jsx +257 -0
  44. package/ui/src/components/InlineChatView.jsx +158 -0
  45. package/ui/src/components/InputBar.jsx +476 -0
  46. package/ui/src/components/OnboardingModal.jsx +112 -0
  47. package/ui/src/components/OutputPanel.jsx +658 -0
  48. package/ui/src/components/PlanProposalPanel.jsx +177 -0
  49. package/ui/src/components/RightPanel.jsx +951 -0
  50. package/ui/src/components/SettingsView.jsx +186 -0
  51. package/ui/src/components/Sidebar.jsx +247 -0
  52. package/ui/src/components/TestingPane.jsx +198 -0
  53. package/ui/src/components/testing/ApiTesterPanel.jsx +187 -0
  54. package/ui/src/components/testing/LogViewerPanel.jsx +64 -0
  55. package/ui/src/components/testing/TerminalPanel.jsx +104 -0
  56. package/ui/src/components/testing/WebPreviewPanel.jsx +78 -0
  57. package/ui/src/hooks/useAgents.js +81 -0
  58. package/ui/src/hooks/useConversation.js +252 -0
  59. package/ui/src/hooks/useTasks.js +161 -0
  60. package/ui/src/hooks/useWorkspace.js +259 -0
  61. package/ui/src/lib/agentIcon.js +10 -0
  62. package/ui/src/lib/conversationUtils.js +575 -0
  63. package/ui/src/main.jsx +10 -0
  64. package/ui/src/styles.css +6899 -0
  65. package/ui/vite.config.mjs +6 -0
@@ -0,0 +1,142 @@
1
+ import execa, { type ExecaChildProcess } from "execa";
2
+
3
+ type CommonOptions = {
4
+ cwd: string;
5
+ env?: NodeJS.ProcessEnv;
6
+ };
7
+
8
+ export type CommandResult = {
9
+ stdout: string;
10
+ stderr: string;
11
+ exitCode: number;
12
+ };
13
+
14
+ export async function runCommand(input: {
15
+ command: string;
16
+ args?: string[];
17
+ cwd: string;
18
+ env?: NodeJS.ProcessEnv;
19
+ }): Promise<string> {
20
+ const result = await runCommandDetailed(input);
21
+ return result.stdout;
22
+ }
23
+
24
+ export async function runCommandDetailed(input: {
25
+ command: string;
26
+ args?: string[];
27
+ cwd: string;
28
+ env?: NodeJS.ProcessEnv;
29
+ }): Promise<CommandResult> {
30
+ const args = input.args ?? [];
31
+ const result = await execa(input.command, args, {
32
+ cwd: input.cwd,
33
+ env: input.env,
34
+ reject: false,
35
+ stdout: "pipe",
36
+ stderr: "pipe",
37
+ });
38
+
39
+ const exitCode = result.exitCode ?? -1;
40
+ if (exitCode !== 0) {
41
+ throw new Error(
42
+ `${input.command} ${args.join(" ")} failed (${exitCode}) in ${input.cwd}: ${
43
+ result.stderr.trim() || "unknown error"
44
+ }`,
45
+ );
46
+ }
47
+
48
+ return {
49
+ stdout: result.stdout,
50
+ stderr: result.stderr,
51
+ exitCode,
52
+ };
53
+ }
54
+
55
+ export async function runShellCommandDetailed(input: {
56
+ command: string;
57
+ cwd: string;
58
+ env?: NodeJS.ProcessEnv;
59
+ }): Promise<CommandResult> {
60
+ const shellCommand =
61
+ process.platform === "win32" ? "cmd.exe" : "sh";
62
+ const shellArgs =
63
+ process.platform === "win32"
64
+ ? ["/d", "/s", "/c", input.command]
65
+ : ["-lc", input.command];
66
+
67
+ const result = await execa(shellCommand, shellArgs, {
68
+ cwd: input.cwd,
69
+ env: input.env,
70
+ reject: false,
71
+ stdout: "pipe",
72
+ stderr: "pipe",
73
+ });
74
+
75
+ return {
76
+ stdout: result.stdout,
77
+ stderr: result.stderr,
78
+ exitCode: result.exitCode ?? -1,
79
+ };
80
+ }
81
+
82
+ export function spawnCommand(input: {
83
+ command: string;
84
+ args?: string[];
85
+ cwd: string;
86
+ env?: NodeJS.ProcessEnv;
87
+ stdio?: "pipe" | "inherit";
88
+ stdin?: "pipe" | "ignore" | "inherit";
89
+ }): ExecaChildProcess<string> {
90
+ const args = input.args ?? [];
91
+ const stdio = input.stdio ?? "pipe";
92
+ const stdioOption = input.stdin
93
+ ? [input.stdin, stdio, stdio] as const
94
+ : stdio;
95
+ return execa(input.command, args, {
96
+ cwd: input.cwd,
97
+ env: input.env,
98
+ reject: false,
99
+ stdio: stdioOption,
100
+ buffer: stdio === "pipe",
101
+ });
102
+ }
103
+
104
+ export function spawnShellCommand(input: {
105
+ command: string;
106
+ cwd: string;
107
+ env?: NodeJS.ProcessEnv;
108
+ stdio?: "pipe" | "inherit";
109
+ }): ExecaChildProcess<string> {
110
+ const stdio = input.stdio ?? "pipe";
111
+ const shellCommand = process.platform === "win32" ? "cmd.exe" : "sh";
112
+ const shellArgs =
113
+ process.platform === "win32"
114
+ ? ["/d", "/s", "/c", input.command]
115
+ : ["-lc", input.command];
116
+
117
+ return execa(shellCommand, shellArgs, {
118
+ cwd: input.cwd,
119
+ env: input.env,
120
+ reject: false,
121
+ stdio,
122
+ buffer: stdio === "pipe",
123
+ });
124
+ }
125
+
126
+ export function spawnNodeCommand(input: {
127
+ modulePath: string;
128
+ args?: string[];
129
+ cwd: string;
130
+ env?: NodeJS.ProcessEnv;
131
+ stdio?: "pipe" | "inherit";
132
+ }): ExecaChildProcess<string> {
133
+ const args = input.args ?? [];
134
+ const stdio = input.stdio ?? "pipe";
135
+ return execa(process.execPath, [input.modulePath, ...args], {
136
+ cwd: input.cwd,
137
+ env: input.env,
138
+ reject: false,
139
+ stdio,
140
+ buffer: stdio === "pipe",
141
+ });
142
+ }
@@ -0,0 +1,167 @@
1
+ import type { PGlite } from "@electric-sql/pglite";
2
+ import type { HarnessId } from "../conversation/types";
3
+
4
+ export type AgentRow = {
5
+ id: string;
6
+ name: string;
7
+ description: string | null;
8
+ status: string;
9
+ agent_file: string | null;
10
+ emoji: string | null;
11
+ harness: HarnessId;
12
+ model_label: string;
13
+ model_id: string;
14
+ };
15
+
16
+ export async function listAgents(db: PGlite): Promise<AgentRow[]> {
17
+ const result = await db.query<AgentRow>(
18
+ `SELECT id, name, description, status, agent_file, emoji, harness, model_label, model_id
19
+ FROM agents ORDER BY name ASC`,
20
+ );
21
+ return result.rows;
22
+ }
23
+
24
+ export async function getAgentById(
25
+ db: PGlite,
26
+ id: string,
27
+ ): Promise<{
28
+ id: string;
29
+ name: string;
30
+ harness: HarnessId;
31
+ model_label: string;
32
+ model_id: string;
33
+ agent_file: string | null;
34
+ } | null> {
35
+ const result = await db.query<{
36
+ id: string;
37
+ name: string;
38
+ harness: HarnessId;
39
+ model_label: string;
40
+ model_id: string;
41
+ agent_file: string | null;
42
+ }>(
43
+ `SELECT id, name, harness, model_label, model_id, agent_file
44
+ FROM agents WHERE id = $1 LIMIT 1`,
45
+ [id],
46
+ );
47
+ return result.rows[0] ?? null;
48
+ }
49
+
50
+ export async function insertAgent(
51
+ db: PGlite,
52
+ input: {
53
+ id: string;
54
+ name: string;
55
+ description: string | null;
56
+ agent_file: string;
57
+ emoji: string;
58
+ harness: HarnessId;
59
+ model_label: string;
60
+ model_id: string;
61
+ },
62
+ ): Promise<void> {
63
+ await db.query(
64
+ `INSERT INTO agents (id, name, description, status, agent_file, emoji, harness, model_label, model_id)
65
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
66
+ [input.id, input.name, input.description, "idle", input.agent_file, input.emoji, input.harness, input.model_label, input.model_id],
67
+ );
68
+ }
69
+
70
+ export async function updateAgent(
71
+ db: PGlite,
72
+ input: {
73
+ id: string;
74
+ name: string;
75
+ description: string | null;
76
+ agent_file: string;
77
+ emoji: string;
78
+ harness: HarnessId;
79
+ model_label: string;
80
+ model_id: string;
81
+ },
82
+ ): Promise<number> {
83
+ const result = await db.query(
84
+ `UPDATE agents
85
+ SET name = $2, description = $3, agent_file = $4, emoji = $5,
86
+ harness = $6, model_label = $7, model_id = $8
87
+ WHERE id = $1`,
88
+ [input.id, input.name, input.description, input.agent_file, input.emoji, input.harness, input.model_label, input.model_id],
89
+ );
90
+ return result.affectedRows ?? 0;
91
+ }
92
+
93
+ export async function deleteAgent(db: PGlite, id: string): Promise<number> {
94
+ await db.query(`UPDATE tasks SET assigned_to = NULL WHERE assigned_to = $1`, [id]);
95
+ const result = await db.query(`DELETE FROM agents WHERE id = $1`, [id]);
96
+ return result.affectedRows ?? 0;
97
+ }
98
+
99
+ export async function generateNextAgentId(db: PGlite, name: string): Promise<string> {
100
+ const base = normalizeAgentIdBase(name);
101
+ const likePattern = `${base}-%`;
102
+ const existing = await db.query<{ id: string }>(
103
+ `SELECT id FROM agents WHERE id = $1 OR id LIKE $2`,
104
+ [base, likePattern],
105
+ );
106
+
107
+ if (existing.rows.length === 0) {
108
+ return base;
109
+ }
110
+
111
+ let maxSuffix = -1;
112
+ const suffixRegex = new RegExp(`^${escapeRegex(base)}-(\\d+)$`);
113
+
114
+ for (const row of existing.rows) {
115
+ if (row.id === base) {
116
+ maxSuffix = Math.max(maxSuffix, 0);
117
+ continue;
118
+ }
119
+ const match = row.id.match(suffixRegex);
120
+ if (!match) continue;
121
+ const parsed = Number.parseInt(match[1], 10);
122
+ if (Number.isInteger(parsed)) {
123
+ maxSuffix = Math.max(maxSuffix, parsed);
124
+ }
125
+ }
126
+
127
+ return `${base}-${Math.max(1, maxSuffix + 1)}`;
128
+ }
129
+
130
+ export async function listPlanWorkerCandidates(
131
+ db: PGlite,
132
+ defaultAgentId: string,
133
+ ): Promise<Array<{
134
+ id: string;
135
+ name: string;
136
+ description: string | null;
137
+ harness: HarnessId;
138
+ model_label: string;
139
+ }>> {
140
+ const result = await db.query<{
141
+ id: string;
142
+ name: string;
143
+ description: string | null;
144
+ harness: HarnessId;
145
+ model_label: string;
146
+ }>(
147
+ `SELECT id, name, description, harness, model_label
148
+ FROM agents WHERE id <> $1 ORDER BY name ASC`,
149
+ [defaultAgentId],
150
+ );
151
+ return result.rows;
152
+ }
153
+
154
+ function normalizeAgentIdBase(name: string): string {
155
+ const normalized = name
156
+ .trim()
157
+ .toLowerCase()
158
+ .replace(/\s+/g, "_")
159
+ .replace(/[^a-z0-9_-]/g, "_")
160
+ .replace(/_+/g, "_")
161
+ .replace(/^[_-]+|[_-]+$/g, "");
162
+ return normalized || "agent";
163
+ }
164
+
165
+ function escapeRegex(input: string): string {
166
+ return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
167
+ }
@@ -0,0 +1,299 @@
1
+ import type { PGlite } from "@electric-sql/pglite";
2
+
3
+ export type ConnectorListRow = {
4
+ id: string;
5
+ name: string;
6
+ display_name: string | null;
7
+ description: string | null;
8
+ provider: string;
9
+ status: string;
10
+ auth_type: string;
11
+ oauth_client_id: string | null;
12
+ oauth_client_secret: string | null;
13
+ };
14
+
15
+ export type ConnectorAuthRow = {
16
+ id: string;
17
+ name: string;
18
+ oauth_client_id: string | null;
19
+ oauth_client_secret: string | null;
20
+ oauth_auth_url: string;
21
+ scopes: string;
22
+ redirect_uri: string;
23
+ auth_type: string;
24
+ };
25
+
26
+ export type ConnectorTokenRow = {
27
+ id: string;
28
+ oauth_client_id: string;
29
+ oauth_client_secret: string;
30
+ oauth_token_url: string;
31
+ redirect_uri: string;
32
+ };
33
+
34
+ export async function listConnectors(db: PGlite): Promise<ConnectorListRow[]> {
35
+ const result = await db.query<ConnectorListRow>(
36
+ `SELECT id, name, display_name, description, provider, status, auth_type,
37
+ oauth_client_id, oauth_client_secret
38
+ FROM connectors ORDER BY name ASC`,
39
+ );
40
+ return result.rows;
41
+ }
42
+
43
+ export async function getConnectorAuthInfo(
44
+ db: PGlite,
45
+ connectorId: string,
46
+ ): Promise<ConnectorAuthRow | null> {
47
+ const result = await db.query<ConnectorAuthRow>(
48
+ `SELECT id, name, oauth_client_id, oauth_client_secret, oauth_auth_url,
49
+ scopes, redirect_uri, auth_type
50
+ FROM connectors WHERE id = $1 LIMIT 1`,
51
+ [connectorId],
52
+ );
53
+ return result.rows[0] ?? null;
54
+ }
55
+
56
+ export async function getConnectorForConfig(
57
+ db: PGlite,
58
+ connectorId: string,
59
+ ): Promise<{ id: string; name: string; auth_type: string } | null> {
60
+ const result = await db.query<{ id: string; name: string; auth_type: string }>(
61
+ `SELECT id, name, auth_type FROM connectors WHERE id = $1 LIMIT 1`,
62
+ [connectorId],
63
+ );
64
+ return result.rows[0] ?? null;
65
+ }
66
+
67
+ export async function setConnectorApiKeyToken(
68
+ db: PGlite,
69
+ connectorId: string,
70
+ accessToken: string,
71
+ ): Promise<void> {
72
+ await db.query(
73
+ `UPDATE connectors SET access_token = $1, status = 'connected' WHERE id = $2`,
74
+ [accessToken, connectorId],
75
+ );
76
+ }
77
+
78
+ export async function updateConnectorOAuthConfig(
79
+ db: PGlite,
80
+ connectorId: string,
81
+ updates: { oauth_client_id?: string | null; oauth_client_secret?: string | null },
82
+ ): Promise<void> {
83
+ const sets: string[] = [];
84
+ const params: unknown[] = [connectorId];
85
+ let paramIndex = 2;
86
+
87
+ if (updates.oauth_client_id !== undefined) {
88
+ sets.push(`oauth_client_id = $${paramIndex++}`);
89
+ params.push(updates.oauth_client_id || null);
90
+ }
91
+ if (updates.oauth_client_secret !== undefined) {
92
+ sets.push(`oauth_client_secret = $${paramIndex++}`);
93
+ params.push(updates.oauth_client_secret || null);
94
+ }
95
+
96
+ if (sets.length > 0) {
97
+ sets.push(`status = 'disconnected'`);
98
+ await db.query(`UPDATE connectors SET ${sets.join(", ")} WHERE id = $1`, params);
99
+ }
100
+ }
101
+
102
+ export async function connectorExists(db: PGlite, connectorId: string): Promise<boolean> {
103
+ const result = await db.query<{ id: string }>(
104
+ `SELECT id FROM connectors WHERE id = $1 LIMIT 1`,
105
+ [connectorId],
106
+ );
107
+ return result.rows.length > 0;
108
+ }
109
+
110
+ export async function storeConnectorTokens(
111
+ db: PGlite,
112
+ connectorId: string,
113
+ accessToken: string,
114
+ refreshToken: string | null,
115
+ tokenExpiresAt: string,
116
+ ): Promise<void> {
117
+ await db.query(
118
+ `UPDATE connectors
119
+ SET access_token = $1, refresh_token = $2, token_expires_at = $3, status = 'connected'
120
+ WHERE id = $4`,
121
+ [accessToken, refreshToken, tokenExpiresAt, connectorId],
122
+ );
123
+ }
124
+
125
+ export async function disconnectConnector(db: PGlite, connectorId: string): Promise<void> {
126
+ await db.query(
127
+ `UPDATE connectors
128
+ SET status = 'disconnected', access_token = NULL,
129
+ refresh_token = NULL, token_expires_at = NULL
130
+ WHERE id = $1`,
131
+ [connectorId],
132
+ );
133
+ }
134
+
135
+ export async function refreshConnectorToken(
136
+ db: PGlite,
137
+ connector: {
138
+ id: string;
139
+ name: string;
140
+ access_token: string | null;
141
+ refresh_token: string | null;
142
+ token_expires_at: string | null;
143
+ oauth_token_url: string;
144
+ oauth_client_id: string | null;
145
+ oauth_client_secret: string | null;
146
+ auth_type: string;
147
+ },
148
+ proxyUrl: string,
149
+ proxyProvider: string | undefined,
150
+ hasCustomCredentials: boolean,
151
+ ): Promise<string> {
152
+ if (connector.auth_type === "api_key") {
153
+ return connector.access_token!;
154
+ }
155
+
156
+ if (connector.token_expires_at && new Date(connector.token_expires_at) > new Date()) {
157
+ return connector.access_token!;
158
+ }
159
+
160
+ if (!connector.refresh_token) {
161
+ throw new Error("No refresh token available — connector needs re-authorization");
162
+ }
163
+
164
+ let response: Response;
165
+
166
+ if (proxyProvider && !hasCustomCredentials) {
167
+ response = await fetch(`${proxyUrl}/refresh/${proxyProvider}`, {
168
+ method: "POST",
169
+ headers: { "Content-Type": "application/json" },
170
+ body: JSON.stringify({ refresh_token: connector.refresh_token }),
171
+ });
172
+ } else {
173
+ response = await fetch(connector.oauth_token_url, {
174
+ method: "POST",
175
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
176
+ body: new URLSearchParams({
177
+ grant_type: "refresh_token",
178
+ refresh_token: connector.refresh_token,
179
+ client_id: connector.oauth_client_id!,
180
+ client_secret: connector.oauth_client_secret!,
181
+ }),
182
+ });
183
+ }
184
+
185
+ if (!response.ok) {
186
+ await db.query(
187
+ `UPDATE connectors SET status = 'disconnected', access_token = NULL,
188
+ refresh_token = NULL, token_expires_at = NULL WHERE id = $1`,
189
+ [connector.id],
190
+ );
191
+ throw new Error("Token refresh failed — connector disconnected");
192
+ }
193
+
194
+ const data = (await response.json()) as {
195
+ access_token: string;
196
+ refresh_token?: string;
197
+ expires_in?: number;
198
+ };
199
+
200
+ await db.query(
201
+ `UPDATE connectors SET access_token = $1, refresh_token = COALESCE($2, refresh_token),
202
+ token_expires_at = $3 WHERE id = $4`,
203
+ [
204
+ data.access_token,
205
+ data.refresh_token ?? null,
206
+ new Date(Date.now() + (data.expires_in ?? 3600) * 1000).toISOString(),
207
+ connector.id,
208
+ ],
209
+ );
210
+
211
+ return data.access_token;
212
+ }
213
+
214
+ export async function getConnectorName(db: PGlite, connectorId: string): Promise<string | null> {
215
+ const result = await db.query<{ name: string }>(
216
+ `SELECT name FROM connectors WHERE id = $1 LIMIT 1`,
217
+ [connectorId],
218
+ );
219
+ return result.rows[0]?.name ?? null;
220
+ }
221
+
222
+ export async function getAgentConnectors(
223
+ db: PGlite,
224
+ agentId: string,
225
+ ): Promise<Array<{ id: string; name: string; description: string | null; status: string }>> {
226
+ const result = await db.query<{
227
+ id: string;
228
+ name: string;
229
+ description: string | null;
230
+ status: string;
231
+ }>(
232
+ `SELECT c.id, c.name, c.description, c.status
233
+ FROM connectors c
234
+ JOIN agent_connectors ac ON ac.connector_id = c.id
235
+ WHERE ac.agent_id = $1
236
+ ORDER BY c.name ASC`,
237
+ [agentId],
238
+ );
239
+ return result.rows;
240
+ }
241
+
242
+ export async function assignAgentConnector(
243
+ db: PGlite,
244
+ agentId: string,
245
+ connectorId: string,
246
+ ): Promise<void> {
247
+ await db.query(
248
+ `INSERT INTO agent_connectors (agent_id, connector_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`,
249
+ [agentId, connectorId],
250
+ );
251
+ }
252
+
253
+ export async function unassignAgentConnector(
254
+ db: PGlite,
255
+ agentId: string,
256
+ connectorId: string,
257
+ ): Promise<number> {
258
+ const result = await db.query(
259
+ `DELETE FROM agent_connectors WHERE agent_id = $1 AND connector_id = $2`,
260
+ [agentId, connectorId],
261
+ );
262
+ return result.affectedRows ?? 0;
263
+ }
264
+
265
+ export async function getAgentConnectorContent(
266
+ db: PGlite,
267
+ agentId: string,
268
+ connectorId: string,
269
+ ): Promise<string | null> {
270
+ const result = await db.query<{ content: string }>(
271
+ `SELECT c.content
272
+ FROM connectors c
273
+ JOIN agent_connectors ac ON ac.connector_id = c.id
274
+ WHERE ac.agent_id = $1 AND c.id = $2
275
+ LIMIT 1`,
276
+ [agentId, connectorId],
277
+ );
278
+ return result.rows[0]?.content ?? null;
279
+ }
280
+
281
+ export async function getConnectorTokenInfo(
282
+ db: PGlite,
283
+ connectorId: string,
284
+ ): Promise<ConnectorTokenRow | null> {
285
+ const result = await db.query<ConnectorTokenRow>(
286
+ `SELECT id, oauth_client_id, oauth_client_secret, oauth_token_url, redirect_uri
287
+ FROM connectors WHERE id = $1 LIMIT 1`,
288
+ [connectorId],
289
+ );
290
+ return result.rows[0] ?? null;
291
+ }
292
+
293
+ export async function agentExists(db: PGlite, agentId: string): Promise<boolean> {
294
+ const result = await db.query<{ id: string }>(
295
+ `SELECT id FROM agents WHERE id = $1 LIMIT 1`,
296
+ [agentId],
297
+ );
298
+ return result.rows.length > 0;
299
+ }
@@ -0,0 +1,28 @@
1
+ import type { PGlite } from "@electric-sql/pglite";
2
+
3
+ export type HumanRow = {
4
+ id: string;
5
+ emoji: string;
6
+ created_at: string;
7
+ };
8
+
9
+ export async function listHumans(db: PGlite): Promise<HumanRow[]> {
10
+ const result = await db.query<HumanRow>(
11
+ `SELECT id, emoji, created_at FROM humans ORDER BY created_at ASC`,
12
+ );
13
+ return result.rows;
14
+ }
15
+
16
+ export async function insertHuman(db: PGlite, id: string, emoji: string): Promise<void> {
17
+ await db.query(`INSERT INTO humans (id, emoji) VALUES ($1, $2)`, [id, emoji]);
18
+ }
19
+
20
+ export async function updateHuman(db: PGlite, id: string, emoji: string): Promise<number> {
21
+ const result = await db.query(`UPDATE humans SET emoji = $2 WHERE id = $1`, [id, emoji]);
22
+ return result.affectedRows ?? 0;
23
+ }
24
+
25
+ export async function deleteHuman(db: PGlite, id: string): Promise<number> {
26
+ const result = await db.query(`DELETE FROM humans WHERE id = $1`, [id]);
27
+ return result.affectedRows ?? 0;
28
+ }