@rubytech/taskmaster 1.0.63 → 1.0.65
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/dist/agents/pi-embedded-runner/compact.js +1 -1
- package/dist/agents/pi-embedded-runner/history.js +57 -15
- package/dist/agents/pi-embedded-runner/run/attempt.js +23 -5
- package/dist/agents/pi-embedded-runner/run.js +6 -31
- package/dist/agents/pi-embedded-runner.js +1 -1
- package/dist/agents/system-prompt.js +20 -0
- package/dist/agents/taskmaster-tools.js +4 -0
- package/dist/agents/tool-policy.js +2 -0
- package/dist/agents/tools/message-history-tool.js +436 -0
- package/dist/agents/tools/sessions-history-tool.js +1 -0
- package/dist/build-info.json +3 -3
- package/dist/config/zod-schema.js +10 -0
- package/dist/control-ui/assets/index-DmifehTc.css +1 -0
- package/dist/control-ui/assets/index-o5Xs9S4u.js +3166 -0
- package/dist/control-ui/assets/index-o5Xs9S4u.js.map +1 -0
- package/dist/control-ui/index.html +2 -2
- package/dist/gateway/config-reload.js +1 -0
- package/dist/gateway/control-ui.js +173 -0
- package/dist/gateway/net.js +16 -0
- package/dist/gateway/protocol/client-info.js +1 -0
- package/dist/gateway/protocol/schema/logs-chat.js +3 -0
- package/dist/gateway/protocol/schema/sessions-transcript.js +1 -3
- package/dist/gateway/public-chat/deliver-otp.js +9 -0
- package/dist/gateway/public-chat/otp.js +60 -0
- package/dist/gateway/public-chat/session.js +45 -0
- package/dist/gateway/server/ws-connection/message-handler.js +17 -4
- package/dist/gateway/server-chat.js +22 -0
- package/dist/gateway/server-http.js +21 -3
- package/dist/gateway/server-methods/chat.js +38 -5
- package/dist/gateway/server-methods/public-chat.js +110 -0
- package/dist/gateway/server-methods/sessions-transcript.js +29 -46
- package/dist/gateway/server-methods.js +17 -0
- package/dist/hooks/bundled/conversation-archive/handler.js +23 -6
- package/dist/infra/session-recovery.js +1 -3
- package/dist/plugins/runtime/index.js +2 -0
- package/dist/utils/message-channel.js +3 -0
- package/package.json +1 -1
- package/taskmaster-docs/USER-GUIDE.md +185 -5
- package/dist/control-ui/assets/index-BPvR6pln.js +0 -3021
- package/dist/control-ui/assets/index-BPvR6pln.js.map +0 -1
- package/dist/control-ui/assets/index-mweBpmCT.css +0 -1
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Type } from "@sinclair/typebox";
|
|
4
|
+
import { resolveAgentWorkspaceDir, resolveSessionAgentId } from "../agent-scope.js";
|
|
5
|
+
import { jsonResult, readNumberParam, readStringParam } from "./common.js";
|
|
6
|
+
/**
|
|
7
|
+
* Schema for the message_history tool.
|
|
8
|
+
*
|
|
9
|
+
* - source: Identifies whose conversation to retrieve.
|
|
10
|
+
* "current" (default) resolves from the agent's own session key.
|
|
11
|
+
* "admin" reads admin/webchat conversations.
|
|
12
|
+
* A phone number (e.g. "+447857934268") reads that user's DM archive.
|
|
13
|
+
* A group ID reads group conversation archive.
|
|
14
|
+
* "discover" lists available conversation sources.
|
|
15
|
+
* "all" retrieves messages across all accessible conversations.
|
|
16
|
+
*
|
|
17
|
+
* - limit: Number of messages to return (default 30, max 500).
|
|
18
|
+
*
|
|
19
|
+
* - date: Optional YYYY-MM-DD filter. Without it, returns the most recent messages.
|
|
20
|
+
* - search: Case-insensitive substring filter on message text.
|
|
21
|
+
* - after/before: Time-range filter (HH:MM) within a date.
|
|
22
|
+
* - channel: Filter by channel (e.g. "whatsapp", "webchat", "imessage").
|
|
23
|
+
*/
|
|
24
|
+
const MessageHistorySchema = Type.Object({
|
|
25
|
+
source: Type.Optional(Type.String({
|
|
26
|
+
description: 'Whose conversation to retrieve: "current" (this session, default), "admin" (admin/webchat), ' +
|
|
27
|
+
'a phone number (e.g. "+447857934268") for a WhatsApp DM, a group JID for group chat, ' +
|
|
28
|
+
'"discover" to list available conversations, or "all" for messages across all conversations.',
|
|
29
|
+
})),
|
|
30
|
+
limit: Type.Optional(Type.Number({
|
|
31
|
+
description: "Number of messages to return (default 30, max 500). Returns the most recent.",
|
|
32
|
+
minimum: 1,
|
|
33
|
+
maximum: 500,
|
|
34
|
+
})),
|
|
35
|
+
date: Type.Optional(Type.String({
|
|
36
|
+
description: "Filter to a specific date (YYYY-MM-DD). Without this, returns the most recent messages across all dates.",
|
|
37
|
+
})),
|
|
38
|
+
search: Type.Optional(Type.String({
|
|
39
|
+
description: "Case-insensitive text search. Only messages containing this substring are returned.",
|
|
40
|
+
})),
|
|
41
|
+
after: Type.Optional(Type.String({
|
|
42
|
+
description: 'Include only messages at or after this time (HH:MM, 24h format). Requires "date" to be set.',
|
|
43
|
+
})),
|
|
44
|
+
before: Type.Optional(Type.String({
|
|
45
|
+
description: 'Include only messages before this time (HH:MM, 24h format). Requires "date" to be set.',
|
|
46
|
+
})),
|
|
47
|
+
channel: Type.Optional(Type.String({
|
|
48
|
+
description: 'Filter messages by channel (e.g. "whatsapp", "webchat", "imessage"). Only works for archives written after channel tagging was enabled.',
|
|
49
|
+
})),
|
|
50
|
+
});
|
|
51
|
+
const DATE_HEADER = /^## (\d{4}-\d{2}-\d{2})\s*$/;
|
|
52
|
+
// Captures: time, role, optional [channel] tag at end of line
|
|
53
|
+
const MESSAGE_HEADER = /^### (\d{2}:\d{2}) — (.+?)(?:\s+\[(\w+)\])?\s*$/;
|
|
54
|
+
/**
|
|
55
|
+
* Parse a conversation archive markdown file into structured messages.
|
|
56
|
+
* The archive format is:
|
|
57
|
+
*
|
|
58
|
+
* ```
|
|
59
|
+
* ## 2026-02-18
|
|
60
|
+
*
|
|
61
|
+
* ### 09:43 — Admin
|
|
62
|
+
* Message text here.
|
|
63
|
+
*
|
|
64
|
+
* ### 09:44 — Assistant
|
|
65
|
+
* Response text here.
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function parseConversationArchive(content) {
|
|
69
|
+
const lines = content.split(/\r?\n/);
|
|
70
|
+
const messages = [];
|
|
71
|
+
let currentDate = "";
|
|
72
|
+
let currentTime = "";
|
|
73
|
+
let currentRole = "";
|
|
74
|
+
let currentChannel;
|
|
75
|
+
let currentTextLines = [];
|
|
76
|
+
function flush() {
|
|
77
|
+
if (currentDate && currentTime && currentRole) {
|
|
78
|
+
const msg = {
|
|
79
|
+
date: currentDate,
|
|
80
|
+
time: currentTime,
|
|
81
|
+
role: currentRole,
|
|
82
|
+
text: currentTextLines.join("\n").trim(),
|
|
83
|
+
};
|
|
84
|
+
if (currentChannel)
|
|
85
|
+
msg.channel = currentChannel;
|
|
86
|
+
messages.push(msg);
|
|
87
|
+
}
|
|
88
|
+
currentTime = "";
|
|
89
|
+
currentRole = "";
|
|
90
|
+
currentChannel = undefined;
|
|
91
|
+
currentTextLines = [];
|
|
92
|
+
}
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
const dateMatch = line.match(DATE_HEADER);
|
|
95
|
+
if (dateMatch) {
|
|
96
|
+
flush();
|
|
97
|
+
currentDate = dateMatch[1];
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const msgMatch = line.match(MESSAGE_HEADER);
|
|
101
|
+
if (msgMatch) {
|
|
102
|
+
flush();
|
|
103
|
+
currentTime = msgMatch[1];
|
|
104
|
+
currentRole = msgMatch[2];
|
|
105
|
+
currentChannel = msgMatch[3] ?? undefined;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// Accumulate text lines for the current message
|
|
109
|
+
if (currentRole) {
|
|
110
|
+
currentTextLines.push(line);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Flush final message
|
|
114
|
+
flush();
|
|
115
|
+
return messages;
|
|
116
|
+
}
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// File resolution
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
/**
|
|
121
|
+
* Resolve the memory subdirectory for a given source identifier.
|
|
122
|
+
*/
|
|
123
|
+
function resolveArchiveSubdir(params) {
|
|
124
|
+
const { source, sessionKey, isAdminAgent } = params;
|
|
125
|
+
if (source === "admin") {
|
|
126
|
+
return { subdir: "admin" };
|
|
127
|
+
}
|
|
128
|
+
// Phone number — DM archive
|
|
129
|
+
if (source.startsWith("+")) {
|
|
130
|
+
const subdir = isAdminAgent ? "admin" : `users/${source}`;
|
|
131
|
+
return { subdir };
|
|
132
|
+
}
|
|
133
|
+
// Group JID
|
|
134
|
+
if (source.includes("@g.us")) {
|
|
135
|
+
return { subdir: `groups/${source}` };
|
|
136
|
+
}
|
|
137
|
+
// "current" — resolve from session key
|
|
138
|
+
if (source === "current" || !source) {
|
|
139
|
+
if (!sessionKey) {
|
|
140
|
+
return { subdir: "", error: "no session key available to resolve current conversation" };
|
|
141
|
+
}
|
|
142
|
+
const parts = sessionKey.toLowerCase().split(":").filter(Boolean);
|
|
143
|
+
// admin webchat: agent:{agentId}:main
|
|
144
|
+
if (parts.length === 3 && parts[0] === "agent" && parts[2] === "main") {
|
|
145
|
+
return { subdir: "admin" };
|
|
146
|
+
}
|
|
147
|
+
// DM: agent:{agentId}:dm:{peer} or agent:{agentId}:{channel}:dm:{peer}
|
|
148
|
+
if (parts.length >= 4 && parts[2] === "dm") {
|
|
149
|
+
const peer = parts.slice(3).join(":");
|
|
150
|
+
return { subdir: isAdminAgent ? "admin" : `users/${peer}` };
|
|
151
|
+
}
|
|
152
|
+
if (parts.length >= 5 && parts[3] === "dm") {
|
|
153
|
+
const peer = parts.slice(4).join(":");
|
|
154
|
+
return { subdir: isAdminAgent ? "admin" : `users/${peer}` };
|
|
155
|
+
}
|
|
156
|
+
// Group: agent:{agentId}:{channel}:group:{groupId}
|
|
157
|
+
if (parts.length >= 5 && parts[3] === "group") {
|
|
158
|
+
const groupId = parts.slice(4).join(":");
|
|
159
|
+
return { subdir: `groups/${groupId}` };
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
subdir: "",
|
|
163
|
+
error: `cannot resolve conversation source from session key: ${sessionKey}`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
subdir: "",
|
|
168
|
+
error: `unrecognised source: "${source}". Use "current", "admin", a phone number, or a group JID.`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* List conversation archive files in a directory, sorted newest first.
|
|
173
|
+
*/
|
|
174
|
+
async function listArchiveFiles(conversationsDir) {
|
|
175
|
+
try {
|
|
176
|
+
const entries = await fs.readdir(conversationsDir);
|
|
177
|
+
const mdFiles = entries
|
|
178
|
+
.filter((e) => e.endsWith(".md"))
|
|
179
|
+
.sort()
|
|
180
|
+
.reverse();
|
|
181
|
+
return mdFiles.map((f) => path.join(conversationsDir, f));
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function applyFilters(messages, filters) {
|
|
188
|
+
let result = messages;
|
|
189
|
+
if (filters.dateFilter) {
|
|
190
|
+
const d = filters.dateFilter;
|
|
191
|
+
result = result.filter((m) => m.date === d);
|
|
192
|
+
}
|
|
193
|
+
if (filters.searchFilter) {
|
|
194
|
+
const s = filters.searchFilter.toLowerCase();
|
|
195
|
+
result = result.filter((m) => m.text.toLowerCase().includes(s));
|
|
196
|
+
}
|
|
197
|
+
if (filters.afterFilter) {
|
|
198
|
+
const a = filters.afterFilter;
|
|
199
|
+
result = result.filter((m) => m.time >= a);
|
|
200
|
+
}
|
|
201
|
+
if (filters.beforeFilter) {
|
|
202
|
+
const b = filters.beforeFilter;
|
|
203
|
+
result = result.filter((m) => m.time < b);
|
|
204
|
+
}
|
|
205
|
+
if (filters.channelFilter) {
|
|
206
|
+
const c = filters.channelFilter.toLowerCase();
|
|
207
|
+
result = result.filter((m) => m.channel?.toLowerCase() === c);
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Scan memory directory for available conversation sources.
|
|
213
|
+
* Admin agents see admin/, users/*, groups/*.
|
|
214
|
+
* Public agents see users/*.
|
|
215
|
+
*/
|
|
216
|
+
async function discoverSources(params) {
|
|
217
|
+
const { memoryDir, isAdminAgent } = params;
|
|
218
|
+
const sources = [];
|
|
219
|
+
// Admin conversations
|
|
220
|
+
if (isAdminAgent) {
|
|
221
|
+
const adminConvDir = path.join(memoryDir, "admin", "conversations");
|
|
222
|
+
const adminFiles = await listArchiveFiles(adminConvDir);
|
|
223
|
+
if (adminFiles.length > 0) {
|
|
224
|
+
sources.push({
|
|
225
|
+
source: "admin",
|
|
226
|
+
type: "admin",
|
|
227
|
+
label: "Admin / Webchat",
|
|
228
|
+
lastArchive: path.basename(adminFiles[0], ".md"),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// User conversations
|
|
233
|
+
const usersDir = path.join(memoryDir, "users");
|
|
234
|
+
try {
|
|
235
|
+
const userEntries = await fs.readdir(usersDir, { withFileTypes: true });
|
|
236
|
+
for (const entry of userEntries) {
|
|
237
|
+
if (!entry.isDirectory())
|
|
238
|
+
continue;
|
|
239
|
+
const convDir = path.join(usersDir, entry.name, "conversations");
|
|
240
|
+
const files = await listArchiveFiles(convDir);
|
|
241
|
+
if (files.length > 0) {
|
|
242
|
+
sources.push({
|
|
243
|
+
source: entry.name,
|
|
244
|
+
type: "user",
|
|
245
|
+
label: entry.name,
|
|
246
|
+
lastArchive: path.basename(files[0], ".md"),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// users/ doesn't exist
|
|
253
|
+
}
|
|
254
|
+
// Group conversations
|
|
255
|
+
const groupsDir = path.join(memoryDir, "groups");
|
|
256
|
+
try {
|
|
257
|
+
const groupEntries = await fs.readdir(groupsDir, { withFileTypes: true });
|
|
258
|
+
for (const entry of groupEntries) {
|
|
259
|
+
if (!entry.isDirectory())
|
|
260
|
+
continue;
|
|
261
|
+
const convDir = path.join(groupsDir, entry.name, "conversations");
|
|
262
|
+
const files = await listArchiveFiles(convDir);
|
|
263
|
+
if (files.length > 0) {
|
|
264
|
+
sources.push({
|
|
265
|
+
source: entry.name,
|
|
266
|
+
type: "group",
|
|
267
|
+
label: entry.name,
|
|
268
|
+
lastArchive: path.basename(files[0], ".md"),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// groups/ doesn't exist
|
|
275
|
+
}
|
|
276
|
+
return sources;
|
|
277
|
+
}
|
|
278
|
+
async function readAllConversations(params) {
|
|
279
|
+
const sources = await discoverSources({
|
|
280
|
+
memoryDir: params.memoryDir,
|
|
281
|
+
isAdminAgent: params.isAdminAgent,
|
|
282
|
+
});
|
|
283
|
+
const all = [];
|
|
284
|
+
for (const src of sources) {
|
|
285
|
+
let convDir;
|
|
286
|
+
if (src.type === "admin") {
|
|
287
|
+
convDir = path.join(params.memoryDir, "admin", "conversations");
|
|
288
|
+
}
|
|
289
|
+
else if (src.type === "user") {
|
|
290
|
+
convDir = path.join(params.memoryDir, "users", src.source, "conversations");
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
convDir = path.join(params.memoryDir, "groups", src.source, "conversations");
|
|
294
|
+
}
|
|
295
|
+
const archiveFiles = await listArchiveFiles(convDir);
|
|
296
|
+
for (const filePath of archiveFiles) {
|
|
297
|
+
try {
|
|
298
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
299
|
+
const parsed = parseConversationArchive(content);
|
|
300
|
+
const filtered = applyFilters(parsed, params.filters);
|
|
301
|
+
for (const m of filtered) {
|
|
302
|
+
all.push({ ...m, source: src.source });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
// skip unreadable
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Sort by date+time descending, take the most recent N, then reverse to chronological
|
|
311
|
+
all.sort((a, b) => {
|
|
312
|
+
const cmp = `${a.date}T${a.time}`.localeCompare(`${b.date}T${b.time}`);
|
|
313
|
+
return -cmp; // newest first
|
|
314
|
+
});
|
|
315
|
+
const sliced = all.slice(0, params.clampedLimit);
|
|
316
|
+
sliced.reverse(); // back to chronological
|
|
317
|
+
return sliced;
|
|
318
|
+
}
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
// Tool factory
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
export function createMessageHistoryTool(options) {
|
|
323
|
+
const cfg = options.config;
|
|
324
|
+
if (!cfg)
|
|
325
|
+
return null;
|
|
326
|
+
const agentId = resolveSessionAgentId({
|
|
327
|
+
sessionKey: options.agentSessionKey,
|
|
328
|
+
config: cfg,
|
|
329
|
+
});
|
|
330
|
+
const agentConfig = cfg?.agents?.list?.find((a) => a.id?.toLowerCase() === agentId?.toLowerCase());
|
|
331
|
+
const isAdminAgent = agentConfig?.tools?.profile === "full" || agentConfig?.default === true;
|
|
332
|
+
return {
|
|
333
|
+
label: "Message History",
|
|
334
|
+
name: "message_history",
|
|
335
|
+
description: "Retrieve recent messages from conversation archives with accurate timestamps. " +
|
|
336
|
+
"Use this to look up what was said in any conversation — current session, admin/webchat, " +
|
|
337
|
+
"a specific WhatsApp DM (by phone number), or a group chat (by group JID). " +
|
|
338
|
+
'Pass source="discover" to list all available conversations, or source="all" to get ' +
|
|
339
|
+
"messages across all conversations. Supports text search, time-range, and channel filtering.",
|
|
340
|
+
parameters: MessageHistorySchema,
|
|
341
|
+
execute: async (_toolCallId, params) => {
|
|
342
|
+
const p = params;
|
|
343
|
+
const rawSource = readStringParam(p, "source") ?? "current";
|
|
344
|
+
const limit = readNumberParam(p, "limit") ?? 30;
|
|
345
|
+
const dateFilter = readStringParam(p, "date");
|
|
346
|
+
const searchFilter = readStringParam(p, "search");
|
|
347
|
+
const afterFilter = readStringParam(p, "after");
|
|
348
|
+
const beforeFilter = readStringParam(p, "before");
|
|
349
|
+
const channelFilter = readStringParam(p, "channel");
|
|
350
|
+
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
|
351
|
+
if (!workspaceDir) {
|
|
352
|
+
return jsonResult({ messages: [], error: "no workspace directory configured" });
|
|
353
|
+
}
|
|
354
|
+
const memoryDir = path.join(workspaceDir, "memory");
|
|
355
|
+
const clampedLimit = Math.min(Math.max(1, limit), 500);
|
|
356
|
+
const filters = {
|
|
357
|
+
dateFilter,
|
|
358
|
+
searchFilter,
|
|
359
|
+
afterFilter,
|
|
360
|
+
beforeFilter,
|
|
361
|
+
channelFilter,
|
|
362
|
+
};
|
|
363
|
+
// -- Discovery mode --
|
|
364
|
+
if (rawSource === "discover") {
|
|
365
|
+
const sources = await discoverSources({ memoryDir, isAdminAgent });
|
|
366
|
+
return jsonResult({
|
|
367
|
+
mode: "discover",
|
|
368
|
+
conversations: sources,
|
|
369
|
+
total: sources.length,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
// -- Aggregate mode --
|
|
373
|
+
if (rawSource === "all") {
|
|
374
|
+
const messages = await readAllConversations({
|
|
375
|
+
memoryDir,
|
|
376
|
+
isAdminAgent,
|
|
377
|
+
clampedLimit,
|
|
378
|
+
filters,
|
|
379
|
+
});
|
|
380
|
+
return jsonResult({
|
|
381
|
+
source: "all",
|
|
382
|
+
messages,
|
|
383
|
+
total: messages.length,
|
|
384
|
+
...(dateFilter ? { dateFilter } : {}),
|
|
385
|
+
...(searchFilter ? { searchFilter } : {}),
|
|
386
|
+
...(channelFilter ? { channelFilter } : {}),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
// -- Single-source mode --
|
|
390
|
+
const { subdir, error: subdirError } = resolveArchiveSubdir({
|
|
391
|
+
source: rawSource,
|
|
392
|
+
sessionKey: options.agentSessionKey,
|
|
393
|
+
isAdminAgent,
|
|
394
|
+
});
|
|
395
|
+
if (subdirError || !subdir) {
|
|
396
|
+
return jsonResult({ messages: [], error: subdirError });
|
|
397
|
+
}
|
|
398
|
+
const conversationsDir = path.join(memoryDir, subdir, "conversations");
|
|
399
|
+
const archiveFiles = await listArchiveFiles(conversationsDir);
|
|
400
|
+
if (archiveFiles.length === 0) {
|
|
401
|
+
return jsonResult({
|
|
402
|
+
messages: [],
|
|
403
|
+
source: rawSource,
|
|
404
|
+
info: `no conversation archives found in ${subdir}/conversations/`,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
// Collect messages from archive files (newest files first)
|
|
408
|
+
const allMessages = [];
|
|
409
|
+
for (const filePath of archiveFiles) {
|
|
410
|
+
try {
|
|
411
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
412
|
+
const parsed = parseConversationArchive(content);
|
|
413
|
+
const filtered = applyFilters(parsed, filters);
|
|
414
|
+
allMessages.unshift(...filtered);
|
|
415
|
+
// If we have enough messages and no active filters require scanning all files, stop early
|
|
416
|
+
if (!dateFilter && !searchFilter && allMessages.length >= clampedLimit) {
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
// Skip unreadable files
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Return the most recent N messages
|
|
425
|
+
const result = allMessages.slice(-clampedLimit);
|
|
426
|
+
return jsonResult({
|
|
427
|
+
source: rawSource,
|
|
428
|
+
messages: result,
|
|
429
|
+
total: result.length,
|
|
430
|
+
...(dateFilter ? { dateFilter } : {}),
|
|
431
|
+
...(searchFilter ? { searchFilter } : {}),
|
|
432
|
+
...(channelFilter ? { channelFilter } : {}),
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
}
|
package/dist/build-info.json
CHANGED
|
@@ -553,6 +553,16 @@ export const TaskmasterSchema = z
|
|
|
553
553
|
})
|
|
554
554
|
.strict()
|
|
555
555
|
.optional(),
|
|
556
|
+
publicChat: z
|
|
557
|
+
.object({
|
|
558
|
+
enabled: z.boolean().optional(),
|
|
559
|
+
auth: z
|
|
560
|
+
.union([z.literal("anonymous"), z.literal("verified"), z.literal("choice")])
|
|
561
|
+
.optional(),
|
|
562
|
+
cookieTtlDays: z.number().int().positive().optional(),
|
|
563
|
+
})
|
|
564
|
+
.strict()
|
|
565
|
+
.optional(),
|
|
556
566
|
})
|
|
557
567
|
.strict()
|
|
558
568
|
.superRefine((cfg, ctx) => {
|