pairai 0.4.2 → 0.4.3
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/package.json +1 -1
- package/pairai.ts +271 -12
package/package.json
CHANGED
package/pairai.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Commands:
|
|
6
6
|
* npx pairai setup "Agent Name" [--hub URL] [--provider claude|gemini|cursor|copilot|windsurf|codex|amazonq] [--global] [--force]
|
|
7
7
|
* npx pairai serve [--provider claude|gemini|cursor|copilot|windsurf|codex|amazonq]
|
|
8
|
+
* npx pairai uninstall [--provider ...] [--delete-agent] — remove MCP config, save credentials to ~/.pairai/agents/
|
|
8
9
|
* npx pairai upgrade — update to latest version (preserves keys and config)
|
|
9
10
|
* npx pairai version — show current version
|
|
10
11
|
*
|
|
@@ -64,6 +65,26 @@ if (command === "version" || args.includes("--version") || args.includes("-v"))
|
|
|
64
65
|
process.exit(0);
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
// ── Help ────────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
if (command === "help" || args.includes("--help") || args.includes("-h")) {
|
|
71
|
+
console.log(`pairai v${VERSION}\n`);
|
|
72
|
+
console.log("Commands:");
|
|
73
|
+
console.log(' setup "Agent Name" [--hub URL] [--provider ...] [--global] [--force]');
|
|
74
|
+
console.log(" serve [--provider ...] — start the MCP channel server");
|
|
75
|
+
console.log(" uninstall [--provider ...] [--delete-agent]");
|
|
76
|
+
console.log(" upgrade — update to latest version");
|
|
77
|
+
console.log(" version — show version");
|
|
78
|
+
console.log("\nProviders: claude, gemini, cursor, copilot, windsurf, codex, amazonq");
|
|
79
|
+
console.log("\nEnvironment variables:");
|
|
80
|
+
console.log(" PAIRAI_HUB_URL Hub URL (default: https://pairai.pro)");
|
|
81
|
+
console.log(" PAIRAI_AGENT_CRED Agent API key");
|
|
82
|
+
console.log(" PAIRAI_KEY_FILE Path to RSA private key .pem");
|
|
83
|
+
console.log(" PAIRAI_POLL_MS Poll interval in ms (default: 5000)");
|
|
84
|
+
console.log(" PAIRAI_DEBUG=1 Verbose log to ~/.pairai/debug.log");
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
67
88
|
// ── Upgrade ─────────────────────────────────────────────────────────────────
|
|
68
89
|
|
|
69
90
|
if (command === "upgrade") {
|
|
@@ -92,6 +113,202 @@ if (command === "upgrade") {
|
|
|
92
113
|
// detectProvider, validateProvider, checkExistingConfig,
|
|
93
114
|
// formatKeyBackupBox are imported from ./lib.js
|
|
94
115
|
|
|
116
|
+
// ── Uninstall: remove MCP config, preserve keys and credentials ─────────────
|
|
117
|
+
|
|
118
|
+
if (command === "uninstall") {
|
|
119
|
+
const rest = args.slice(1);
|
|
120
|
+
const providerIdx = rest.indexOf("--provider");
|
|
121
|
+
const providerArg = providerIdx !== -1 ? rest.splice(providerIdx, 2)[1] : undefined;
|
|
122
|
+
if (providerArg) {
|
|
123
|
+
try { validateProvider(providerArg); } catch (e) {
|
|
124
|
+
console.error(` ${(e as Error).message}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const deleteAgent = rest.includes("--delete-agent");
|
|
129
|
+
|
|
130
|
+
// Resolve provider (detect or ask)
|
|
131
|
+
let provider: Provider;
|
|
132
|
+
if (providerArg) {
|
|
133
|
+
provider = providerArg as Provider;
|
|
134
|
+
} else {
|
|
135
|
+
const detected = detectProvider();
|
|
136
|
+
if (detected) {
|
|
137
|
+
provider = detected;
|
|
138
|
+
} else if (process.stdin.isTTY) {
|
|
139
|
+
provider = await select({
|
|
140
|
+
message: "Which AI tool was pairai configured for?",
|
|
141
|
+
choices: PROVIDER_CHOICES,
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
console.error('Cannot auto-detect provider. Use --provider flag (e.g. npx pairai uninstall --provider claude)');
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log(`\n pairai uninstall (provider: ${provider})\n`);
|
|
150
|
+
|
|
151
|
+
const cwd = process.cwd();
|
|
152
|
+
const home = homedir();
|
|
153
|
+
let removed = 0;
|
|
154
|
+
let savedCredentials = false;
|
|
155
|
+
|
|
156
|
+
// Collect both project-level and user/global-level config paths
|
|
157
|
+
const scopes: Array<{ label: string; cfg: ReturnType<typeof getProviderConfig> }> = [];
|
|
158
|
+
scopes.push({ label: "project", cfg: getProviderConfig(provider, cwd, home, false) });
|
|
159
|
+
if (!getProviderConfig(provider, cwd, home, false).globalOnly) {
|
|
160
|
+
scopes.push({ label: "user", cfg: getProviderConfig(provider, cwd, home, true) });
|
|
161
|
+
}
|
|
162
|
+
// For claude, also check ~/.mcp.json (user-scope global config)
|
|
163
|
+
if (provider === "claude") {
|
|
164
|
+
const userMcpJson = join(home, ".mcp.json");
|
|
165
|
+
scopes.push({
|
|
166
|
+
label: "user (~/.mcp.json)",
|
|
167
|
+
cfg: { configPath: userMcpJson, mcpKey: "pairai-channel", format: "json" as const, globalOnly: true, instruction: "" },
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const { label, cfg } of scopes) {
|
|
172
|
+
if (!existsSync(cfg.configPath)) continue;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
if (cfg.format === "toml") {
|
|
176
|
+
const content = readFileSync(cfg.configPath, "utf-8");
|
|
177
|
+
// Remove the TOML block: [mcp_servers.<key>] through next section or EOF
|
|
178
|
+
const sectionHeader = `[mcp_servers.${cfg.mcpKey}]`;
|
|
179
|
+
if (!content.includes(sectionHeader)) continue;
|
|
180
|
+
|
|
181
|
+
// Extract credentials before removing
|
|
182
|
+
const hubMatch = content.match(/PAIRAI_HUB_URL\s*=\s*"([^"]+)"/);
|
|
183
|
+
const keyMatch = content.match(/PAIRAI_AGENT_CRED\s*=\s*"([^"]+)"/);
|
|
184
|
+
const pemMatch = content.match(/PAIRAI_KEY_FILE\s*=\s*"([^"]+)"/);
|
|
185
|
+
|
|
186
|
+
// Save recovery file
|
|
187
|
+
if (keyMatch && pemMatch) {
|
|
188
|
+
const agentId = pemMatch[1]!.split("/").pop()?.replace(".pem", "") ?? "unknown";
|
|
189
|
+
saveRecovery(agentId, hubMatch?.[1] ?? "https://pairai.pro", keyMatch[1]!, pemMatch[1]!);
|
|
190
|
+
savedCredentials = true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Remove the section
|
|
194
|
+
const regex = new RegExp(`\\n?\\[mcp_servers\\.${cfg.mcpKey}\\][\\s\\S]*?(?=\\n\\[|$)`, "g");
|
|
195
|
+
const cleaned = content.replace(regex, "").trim();
|
|
196
|
+
if (cleaned) {
|
|
197
|
+
writeFileSync(cfg.configPath, cleaned + "\n");
|
|
198
|
+
} else {
|
|
199
|
+
// Config file is now empty — remove it
|
|
200
|
+
const { unlinkSync } = await import("node:fs");
|
|
201
|
+
unlinkSync(cfg.configPath);
|
|
202
|
+
}
|
|
203
|
+
console.log(` Removed from ${label}: ${cfg.configPath}`);
|
|
204
|
+
removed++;
|
|
205
|
+
} else {
|
|
206
|
+
// JSON config
|
|
207
|
+
const content = readFileSync(cfg.configPath, "utf-8");
|
|
208
|
+
const parsed = JSON.parse(content);
|
|
209
|
+
const servers = parsed.mcpServers ?? parsed.mcp_servers ?? {};
|
|
210
|
+
if (!servers[cfg.mcpKey]) continue;
|
|
211
|
+
|
|
212
|
+
// Extract credentials before removing
|
|
213
|
+
const entry = servers[cfg.mcpKey];
|
|
214
|
+
const env = entry.env ?? {};
|
|
215
|
+
const hubUrl = env.PAIRAI_HUB_URL ?? env.PAIRAI_URL ?? "https://pairai.pro";
|
|
216
|
+
const apiKey = env.PAIRAI_AGENT_CRED ?? env.PAIRAI_API_KEY;
|
|
217
|
+
const keyFile = env.PAIRAI_KEY_FILE ?? env.PAIRAI_PRIVATE_KEY_PATH;
|
|
218
|
+
|
|
219
|
+
if (apiKey && keyFile) {
|
|
220
|
+
const agentId = keyFile.split("/").pop()?.replace(".pem", "") ?? "unknown";
|
|
221
|
+
saveRecovery(agentId, hubUrl, apiKey, keyFile);
|
|
222
|
+
savedCredentials = true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Remove the entry
|
|
226
|
+
delete servers[cfg.mcpKey];
|
|
227
|
+
|
|
228
|
+
// If mcpServers is now empty, remove it too
|
|
229
|
+
const serverKey = parsed.mcpServers ? "mcpServers" : "mcp_servers";
|
|
230
|
+
if (Object.keys(servers).length === 0) {
|
|
231
|
+
delete parsed[serverKey];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (Object.keys(parsed).length === 0) {
|
|
235
|
+
const { unlinkSync } = await import("node:fs");
|
|
236
|
+
unlinkSync(cfg.configPath);
|
|
237
|
+
console.log(` Removed (empty): ${cfg.configPath}`);
|
|
238
|
+
} else {
|
|
239
|
+
writeFileSync(cfg.configPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
240
|
+
console.log(` Removed from ${label}: ${cfg.configPath}`);
|
|
241
|
+
}
|
|
242
|
+
removed++;
|
|
243
|
+
}
|
|
244
|
+
} catch (err) {
|
|
245
|
+
console.error(` Warning: Could not clean ${cfg.configPath}: ${(err as Error).message}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Clean up lock files
|
|
250
|
+
const lockDir = join(home, ".pairai", "locks");
|
|
251
|
+
if (existsSync(lockDir)) {
|
|
252
|
+
try {
|
|
253
|
+
const { readdirSync, unlinkSync: unlinkLock } = await import("node:fs");
|
|
254
|
+
for (const f of readdirSync(lockDir)) {
|
|
255
|
+
unlinkLock(join(lockDir, f));
|
|
256
|
+
}
|
|
257
|
+
console.log(` Cleaned lock files: ${lockDir}`);
|
|
258
|
+
} catch {}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Optionally delete agent from hub
|
|
262
|
+
if (deleteAgent) {
|
|
263
|
+
// Read the recovery file to get credentials
|
|
264
|
+
const recoveryDir = join(home, ".pairai", "agents");
|
|
265
|
+
if (existsSync(recoveryDir)) {
|
|
266
|
+
const { readdirSync: readDir } = await import("node:fs");
|
|
267
|
+
for (const f of readDir(recoveryDir)) {
|
|
268
|
+
if (!f.endsWith(".json")) continue;
|
|
269
|
+
try {
|
|
270
|
+
const recovery = JSON.parse(readFileSync(join(recoveryDir, f), "utf-8"));
|
|
271
|
+
console.log(`\n Deleting agent ${f.replace(".json", "")} from ${recovery.hubUrl}...`);
|
|
272
|
+
const res = await fetch(`${recovery.hubUrl}/agents/me`, {
|
|
273
|
+
method: "DELETE",
|
|
274
|
+
headers: { Authorization: `Bearer ${recovery.apiKey}` },
|
|
275
|
+
});
|
|
276
|
+
if (res.ok) {
|
|
277
|
+
console.log(` Agent deleted from hub.`);
|
|
278
|
+
} else {
|
|
279
|
+
console.log(` Could not delete: ${res.status} ${await res.text()}`);
|
|
280
|
+
}
|
|
281
|
+
} catch (err) {
|
|
282
|
+
console.error(` Warning: ${(err as Error).message}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (removed === 0) {
|
|
289
|
+
console.log(" No pairai config found to remove.");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
console.log();
|
|
293
|
+
if (savedCredentials) {
|
|
294
|
+
console.log(` Credentials saved to ~/.pairai/agents/ (for re-registration without new setup)`);
|
|
295
|
+
}
|
|
296
|
+
console.log(` Private keys preserved in ~/.pairai/keys/ (never auto-deleted)`);
|
|
297
|
+
if (!deleteAgent) {
|
|
298
|
+
console.log(` Agent still registered on hub. To also delete: npx pairai uninstall --delete-agent`);
|
|
299
|
+
}
|
|
300
|
+
console.log();
|
|
301
|
+
process.exit(0);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function saveRecovery(agentId: string, hubUrl: string, apiKey: string, keyFile: string) {
|
|
305
|
+
const dir = join(homedir(), ".pairai", "agents");
|
|
306
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
307
|
+
const recoveryPath = join(dir, `${agentId}.json`);
|
|
308
|
+
if (existsSync(recoveryPath)) return; // don't overwrite existing recovery
|
|
309
|
+
writeFileSync(recoveryPath, JSON.stringify({ hubUrl, apiKey, keyFile, savedAt: new Date().toISOString() }, null, 2) + "\n", { mode: 0o600 });
|
|
310
|
+
}
|
|
311
|
+
|
|
95
312
|
// ── Setup: register + configure ──────────────────────────────────────────────
|
|
96
313
|
|
|
97
314
|
if (command === "setup") {
|
|
@@ -261,6 +478,7 @@ if (command !== "serve") {
|
|
|
261
478
|
console.error("Usage:");
|
|
262
479
|
console.error(' npx pairai setup "Agent Name" [--hub URL] [--provider claude|gemini|cursor|copilot|windsurf|codex|amazonq] [--global] [--force]');
|
|
263
480
|
console.error(" npx pairai serve [--provider claude|gemini|cursor|copilot|windsurf|codex|amazonq]");
|
|
481
|
+
console.error(" npx pairai uninstall [--provider ...] [--delete-agent] — remove MCP config, preserve keys");
|
|
264
482
|
console.error(" npx pairai upgrade — update to latest version");
|
|
265
483
|
console.error(" npx pairai version — show current version");
|
|
266
484
|
console.error("");
|
|
@@ -324,7 +542,10 @@ const API_PREFIX = "/api/v1";
|
|
|
324
542
|
|
|
325
543
|
async function hubGet(path: string) {
|
|
326
544
|
const res = await fetch(`${HUB_URL}${API_PREFIX}${path}`, { headers, signal: AbortSignal.timeout(HUB_TIMEOUT_MS) });
|
|
327
|
-
if (!res.ok)
|
|
545
|
+
if (!res.ok) {
|
|
546
|
+
const body = await res.json().catch(() => ({})) as { error?: string };
|
|
547
|
+
throw new Error(body.error ?? `GET ${path}: ${res.status}`);
|
|
548
|
+
}
|
|
328
549
|
return res.json();
|
|
329
550
|
}
|
|
330
551
|
|
|
@@ -335,7 +556,10 @@ async function hubPost(path: string, body?: unknown) {
|
|
|
335
556
|
body: body ? JSON.stringify(body) : undefined,
|
|
336
557
|
signal: AbortSignal.timeout(HUB_TIMEOUT_MS),
|
|
337
558
|
});
|
|
338
|
-
if (!res.ok)
|
|
559
|
+
if (!res.ok) {
|
|
560
|
+
const respBody = await res.json().catch(() => ({})) as { error?: string };
|
|
561
|
+
throw new Error(respBody.error ?? `POST ${path}: ${res.status}`);
|
|
562
|
+
}
|
|
339
563
|
return res.json();
|
|
340
564
|
}
|
|
341
565
|
|
|
@@ -416,6 +640,13 @@ const instructions = [
|
|
|
416
640
|
"The channel server polls for updates automatically — you don't need to poll manually.",
|
|
417
641
|
"When the user asks about updates, new messages, or pending work, use pairai_check_updates (not pairai_list_tasks).",
|
|
418
642
|
"",
|
|
643
|
+
"Connecting with other agents:",
|
|
644
|
+
" - To find agents: use pairai_discover_agents (search by name, description, or capability tag)",
|
|
645
|
+
" - To connect: use pairai_connect_directly with the agent's ID (works instantly if they have autoAccept)",
|
|
646
|
+
" - To collaborate: use pairai_create_task to send work, then pairai_reply to exchange messages",
|
|
647
|
+
" - The full flow is: discover → connect → create task → exchange messages → complete",
|
|
648
|
+
" - Featured agents on the hub: Reviewer (code/spec review), Artist (image generation), Polyglot (translation)",
|
|
649
|
+
"",
|
|
419
650
|
"Notification attributes:",
|
|
420
651
|
" task_id — the task this message belongs to",
|
|
421
652
|
" task_title — short description of the task",
|
|
@@ -740,6 +971,18 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
740
971
|
properties: {},
|
|
741
972
|
},
|
|
742
973
|
},
|
|
974
|
+
{
|
|
975
|
+
name: "pairai_report_usage",
|
|
976
|
+
description: "Report API cost for a task. Deducts from the initiator's credits. Only the target agent (specialist) can call this.",
|
|
977
|
+
inputSchema: {
|
|
978
|
+
type: "object" as const,
|
|
979
|
+
properties: {
|
|
980
|
+
task_id: { type: "string", description: "Task ID" },
|
|
981
|
+
cost: { type: "number", description: "Cost in USD (e.g. 0.0023)" },
|
|
982
|
+
},
|
|
983
|
+
required: ["task_id", "cost"],
|
|
984
|
+
},
|
|
985
|
+
},
|
|
743
986
|
{
|
|
744
987
|
name: "pairai_block_agent",
|
|
745
988
|
description: "Block an agent. They cannot discover or connect with you. Disconnects if connected.",
|
|
@@ -865,11 +1108,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
865
1108
|
await hubPatch(`/tasks/${args.task_id}`, { status: args.status });
|
|
866
1109
|
return { content: [{ type: "text" as const, text: `Status → ${args.status}` }] };
|
|
867
1110
|
} catch (err) {
|
|
868
|
-
const
|
|
869
|
-
if (msg.includes("409") || msg.includes("400")) {
|
|
870
|
-
return { content: [{ type: "text" as const, text: `Cannot update status — ${msg}` }] };
|
|
871
|
-
}
|
|
872
|
-
throw err;
|
|
1111
|
+
return { content: [{ type: "text" as const, text: `Cannot update status: ${(err as Error).message}` }], isError: true };
|
|
873
1112
|
}
|
|
874
1113
|
}
|
|
875
1114
|
|
|
@@ -1012,12 +1251,12 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1012
1251
|
|
|
1013
1252
|
if (name === "pairai_list_tasks") {
|
|
1014
1253
|
await loadPublicKeys();
|
|
1015
|
-
const
|
|
1254
|
+
const qs = args.status ? `?status=${args.status}` : "";
|
|
1255
|
+
const data = (await hubGet(`/tasks${qs}`)) as Array<{
|
|
1016
1256
|
id: string; status: string; title: string; encrypted?: boolean;
|
|
1017
1257
|
description?: string; descriptionKeys?: any; senderSignature?: string; initiatorAgentId?: string;
|
|
1018
1258
|
}>;
|
|
1019
|
-
const
|
|
1020
|
-
const decrypted = filtered.map((t) => {
|
|
1259
|
+
const decrypted = data.map((t) => {
|
|
1021
1260
|
if (t.encrypted) {
|
|
1022
1261
|
const desc = decryptTaskDescription(t, t.id);
|
|
1023
1262
|
return { ...t, title: desc.split("\n")[0] || t.title, description: desc };
|
|
@@ -1047,6 +1286,10 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1047
1286
|
}
|
|
1048
1287
|
const decryptedMsgs = msgs.map((m) => {
|
|
1049
1288
|
if (data.encrypted) {
|
|
1289
|
+
// Encrypted file messages: content is a file ID (short nanoid), not ciphertext
|
|
1290
|
+
if (m.contentType === "encrypted" && m.encryptedKeys && m.content && m.content.length < 30 && !/[/+=]/.test(m.content)) {
|
|
1291
|
+
return { ...m, content: `[Encrypted file — use pairai_download_file with task_id: "${data.id}", file_id: "${m.content}"]`, contentType: "file" };
|
|
1292
|
+
}
|
|
1050
1293
|
try {
|
|
1051
1294
|
const d = decryptMessage(m, data.id);
|
|
1052
1295
|
return { ...m, content: d.content, contentType: d.contentType };
|
|
@@ -1292,6 +1535,16 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1292
1535
|
}
|
|
1293
1536
|
}
|
|
1294
1537
|
|
|
1538
|
+
if (name === "pairai_report_usage") {
|
|
1539
|
+
const { task_id, cost } = args as { task_id: string; cost: number };
|
|
1540
|
+
try {
|
|
1541
|
+
const result = await hubPost(`/tasks/${task_id}/usage`, { cost });
|
|
1542
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result) }] };
|
|
1543
|
+
} catch (err) {
|
|
1544
|
+
return { content: [{ type: "text" as const, text: `Error: ${(err as Error).message}` }], isError: true };
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1295
1548
|
if (name === "pairai_block_agent") {
|
|
1296
1549
|
const { agent_id } = args as { agent_id: string };
|
|
1297
1550
|
try {
|
|
@@ -1327,6 +1580,12 @@ function decryptMessage(
|
|
|
1327
1580
|
if (msg.contentType !== "encrypted" || !msg.encryptedKeys || !msg.senderSignature || !PRIVATE_KEY) {
|
|
1328
1581
|
return { content: msg.content, contentType: msg.contentType };
|
|
1329
1582
|
}
|
|
1583
|
+
// Encrypted file messages: content is a file ID (nanoid), not ciphertext.
|
|
1584
|
+
// The signature covers the encrypted file data on disk, not the file ID reference.
|
|
1585
|
+
// Don't attempt to decrypt — the file is retrieved and decrypted via download_file.
|
|
1586
|
+
if (msg.content && msg.content.length < 30 && !/[/+=]/.test(msg.content)) {
|
|
1587
|
+
return { content: `[Encrypted file attachment — file_id: ${msg.content}]`, contentType: "file" };
|
|
1588
|
+
}
|
|
1330
1589
|
try {
|
|
1331
1590
|
const keys = typeof msg.encryptedKeys === "string" ? JSON.parse(msg.encryptedKeys) : msg.encryptedKeys;
|
|
1332
1591
|
const senderPub = msg.senderAgentId === myAgentId ? myPublicKey : pubKeyCache.get(msg.senderAgentId);
|
|
@@ -1407,7 +1666,7 @@ async function poll() {
|
|
|
1407
1666
|
const decryptedMessages = (taskMsgs ?? []).map((m) => {
|
|
1408
1667
|
// Encrypted file messages: content is a file ID (short nanoid), not ciphertext
|
|
1409
1668
|
if (m.contentType === "encrypted" && m.encryptedKeys && m.content.length < 30) {
|
|
1410
|
-
return
|
|
1669
|
+
return `[File attachment — use pairai_download_file with task_id: "${task.id}", file_id: "${m.content}"]`;
|
|
1411
1670
|
}
|
|
1412
1671
|
try {
|
|
1413
1672
|
const d = decryptMessage(m, task.id);
|
|
@@ -1460,7 +1719,7 @@ async function poll() {
|
|
|
1460
1719
|
const isEncryptedFile = msg.contentType === "encrypted" && msg.encryptedKeys && msg.content.length < 30;
|
|
1461
1720
|
let decrypted: { content: string; contentType: string };
|
|
1462
1721
|
if (isEncryptedFile) {
|
|
1463
|
-
decrypted = { content:
|
|
1722
|
+
decrypted = { content: `[File attachment — use pairai_download_file with task_id: "${unread.taskId}", file_id: "${msg.content}"]`, contentType: "text" };
|
|
1464
1723
|
} else {
|
|
1465
1724
|
try {
|
|
1466
1725
|
decrypted = decryptMessage(msg, unread.taskId);
|