open-research 0.1.26 → 1.0.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.
@@ -0,0 +1,15 @@
1
+ // src/lib/ontology/types.ts
2
+ var DEFAULT_CONFIDENCE = {
3
+ source: "established",
4
+ finding: "established",
5
+ claim: "hypothesized",
6
+ question: "questioned",
7
+ method: "established",
8
+ insight: "hypothesized"
9
+ };
10
+ var NOTE_KINDS = ["source", "finding", "claim", "question", "method", "insight"];
11
+
12
+ export {
13
+ DEFAULT_CONFIDENCE,
14
+ NOTE_KINDS
15
+ };
@@ -0,0 +1,515 @@
1
+ import {
2
+ getOpenResearchConfigFile
3
+ } from "./chunk-I5NVYKG7.js";
4
+
5
+ // src/lib/config/store.ts
6
+ import { z } from "zod";
7
+
8
+ // src/lib/fs/json.ts
9
+ import fs from "fs/promises";
10
+ import path from "path";
11
+ async function readJsonFile(filePath, fallback) {
12
+ try {
13
+ const raw = await fs.readFile(filePath, "utf8");
14
+ return JSON.parse(raw);
15
+ } catch (error) {
16
+ const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
17
+ if (code === "ENOENT") {
18
+ return fallback;
19
+ }
20
+ throw error;
21
+ }
22
+ }
23
+ async function writeJsonFile(filePath, value, mode) {
24
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
25
+ await fs.writeFile(filePath, JSON.stringify(value, null, 2), {
26
+ encoding: "utf8",
27
+ mode
28
+ });
29
+ }
30
+
31
+ // src/lib/config/store.ts
32
+ var themeValues = ["dark", "light"];
33
+ var openAIProviderConfigSchema = z.object({
34
+ apiKey: z.string().optional()
35
+ }).optional();
36
+ var openResearchConfigSchema = z.object({
37
+ version: z.literal(1),
38
+ defaults: z.object({
39
+ model: z.string().min(1),
40
+ reasoningEffort: z.enum(["low", "medium", "high", "xhigh"]),
41
+ editPolicy: z.literal("mixed")
42
+ }),
43
+ theme: z.enum(themeValues).default("dark"),
44
+ lastWorkspace: z.string().nullable(),
45
+ providers: z.object({
46
+ openai: openAIProviderConfigSchema
47
+ }).optional(),
48
+ apiKeys: z.object({
49
+ openai: z.string().optional(),
50
+ semanticScholar: z.string().optional(),
51
+ openAlex: z.string().optional(),
52
+ brave: z.string().optional()
53
+ }).optional()
54
+ });
55
+ var DEFAULT_OPEN_RESEARCH_CONFIG = {
56
+ version: 1,
57
+ defaults: {
58
+ model: "gpt-5.4",
59
+ reasoningEffort: "medium",
60
+ editPolicy: "mixed"
61
+ },
62
+ theme: "dark",
63
+ lastWorkspace: null,
64
+ providers: {
65
+ openai: {}
66
+ },
67
+ apiKeys: {}
68
+ };
69
+ function getConfiguredOpenAIApiKey(config) {
70
+ return config?.providers?.openai?.apiKey || config?.apiKeys?.openai;
71
+ }
72
+ function getSemanticScholarApiKey(config) {
73
+ return config?.apiKeys?.semanticScholar || process.env.SEMANTIC_SCHOLAR_API_KEY;
74
+ }
75
+ function getOpenAlexApiKey(config) {
76
+ return config?.apiKeys?.openAlex || process.env.OPENALEX_API_KEY;
77
+ }
78
+ function getBraveApiKey(config) {
79
+ return config?.apiKeys?.brave || process.env.BRAVE_API_KEY;
80
+ }
81
+ async function loadOpenResearchConfig(options) {
82
+ const configFile = getOpenResearchConfigFile(options);
83
+ const config = await readJsonFile(configFile, null);
84
+ if (!config) {
85
+ return null;
86
+ }
87
+ return openResearchConfigSchema.parse(config);
88
+ }
89
+ async function saveOpenResearchConfig(config, options) {
90
+ await writeJsonFile(getOpenResearchConfigFile(options), config);
91
+ }
92
+ async function ensureOpenResearchConfig(options) {
93
+ const existing = await loadOpenResearchConfig(options);
94
+ if (existing) {
95
+ return existing;
96
+ }
97
+ await writeJsonFile(getOpenResearchConfigFile(options), DEFAULT_OPEN_RESEARCH_CONFIG);
98
+ return DEFAULT_OPEN_RESEARCH_CONFIG;
99
+ }
100
+
101
+ // src/lib/agent/tools/fetch-url.ts
102
+ import { load as loadCheerio } from "cheerio";
103
+ var MAX_RESPONSE_BYTES = 512 * 1024;
104
+ var DEFAULT_TIMEOUT_MS = 3e4;
105
+ var MAX_TIMEOUT_MS = 12e4;
106
+ var USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
107
+ function htmlToText(html) {
108
+ const $ = loadCheerio(html);
109
+ $("script, style, noscript, nav, footer, header, aside, iframe, svg").remove();
110
+ const title = $("title").first().text().trim();
111
+ const mainEl = $("main").length > 0 ? $("main") : $("article").length > 0 ? $("article") : $("body");
112
+ const sections = [];
113
+ if (title) sections.push(`# ${title}
114
+ `);
115
+ mainEl.find("h1, h2, h3, h4, h5, h6, p, li, td, th, blockquote, pre, dd, dt, figcaption").each((_, el) => {
116
+ const tag = el.tagName?.toLowerCase() ?? "";
117
+ const text = $(el).text().replace(/\s+/g, " ").trim();
118
+ if (!text) return;
119
+ if (tag === "h1") sections.push(`
120
+ # ${text}`);
121
+ else if (tag === "h2") sections.push(`
122
+ ## ${text}`);
123
+ else if (tag === "h3") sections.push(`
124
+ ### ${text}`);
125
+ else if (tag.startsWith("h")) sections.push(`
126
+ #### ${text}`);
127
+ else if (tag === "li") sections.push(`- ${text}`);
128
+ else if (tag === "blockquote") sections.push(`> ${text}`);
129
+ else if (tag === "pre") sections.push(`\`\`\`
130
+ ${text}
131
+ \`\`\``);
132
+ else sections.push(text);
133
+ });
134
+ if (sections.length <= 1) {
135
+ const bodyText = mainEl.text().replace(/\s+/g, " ").trim();
136
+ if (title) return `# ${title}
137
+
138
+ ${bodyText}`;
139
+ return bodyText;
140
+ }
141
+ return sections.join("\n");
142
+ }
143
+ async function executeFetchUrl(args, signal) {
144
+ const url = args.url.trim();
145
+ if (!url) return "Error: url is required.";
146
+ let parsed;
147
+ try {
148
+ parsed = new URL(url);
149
+ } catch {
150
+ return `Error: Invalid URL: ${url}`;
151
+ }
152
+ if (!["http:", "https:"].includes(parsed.protocol)) {
153
+ return `Error: Only http and https URLs are supported.`;
154
+ }
155
+ const timeout = Math.min(
156
+ Math.max(args.timeout ?? DEFAULT_TIMEOUT_MS, 5e3),
157
+ MAX_TIMEOUT_MS
158
+ );
159
+ const format = args.format ?? "text";
160
+ const timeoutController = new AbortController();
161
+ const timer = setTimeout(() => timeoutController.abort(), timeout);
162
+ const combinedSignal = signal ? AbortSignal.any([signal, timeoutController.signal]) : timeoutController.signal;
163
+ try {
164
+ let response = await fetch(url, {
165
+ headers: { "User-Agent": USER_AGENT, Accept: "text/html,application/json,text/plain,*/*" },
166
+ redirect: "follow",
167
+ signal: combinedSignal
168
+ });
169
+ if (response.status === 403 && response.headers.get("cf-mitigated") === "challenge") {
170
+ response = await fetch(url, {
171
+ headers: { "User-Agent": "open-research-cli/0.1", Accept: "text/html,application/json,text/plain,*/*" },
172
+ redirect: "follow",
173
+ signal: combinedSignal
174
+ });
175
+ }
176
+ if (!response.ok) {
177
+ return `Error: HTTP ${response.status} ${response.statusText}`;
178
+ }
179
+ const finalUrl = response.url;
180
+ const redirectNote = finalUrl && finalUrl !== url ? `(Redirected to: ${finalUrl})
181
+
182
+ ` : "";
183
+ const contentType = response.headers.get("content-type") ?? "";
184
+ if (contentType.includes("image/") || contentType.includes("audio/") || contentType.includes("video/") || contentType.includes("application/octet-stream") || contentType.includes("application/zip")) {
185
+ const length = response.headers.get("content-length");
186
+ return `${redirectNote}Binary content: ${contentType}${length ? ` (${(Number(length) / 1024).toFixed(1)} KB)` : ""}. Use run_command with curl to download.`;
187
+ }
188
+ const reader = response.body?.getReader();
189
+ if (!reader) return "Error: No response body.";
190
+ const chunks = [];
191
+ let totalBytes = 0;
192
+ let truncated = false;
193
+ while (true) {
194
+ const { done, value } = await reader.read();
195
+ if (done) break;
196
+ if (totalBytes + value.length > MAX_RESPONSE_BYTES) {
197
+ const remaining = MAX_RESPONSE_BYTES - totalBytes;
198
+ if (remaining > 0) chunks.push(value.subarray(0, remaining));
199
+ totalBytes = MAX_RESPONSE_BYTES;
200
+ truncated = true;
201
+ reader.cancel();
202
+ break;
203
+ }
204
+ chunks.push(value);
205
+ totalBytes += value.length;
206
+ }
207
+ const raw = new TextDecoder().decode(
208
+ chunks.length === 1 ? chunks[0] : Buffer.concat(chunks)
209
+ );
210
+ const truncSuffix = truncated ? "\n\n(Response truncated to 512 KB)" : "";
211
+ if (contentType.includes("application/json")) {
212
+ try {
213
+ const obj = JSON.parse(raw);
214
+ return redirectNote + JSON.stringify(obj, null, 2) + truncSuffix;
215
+ } catch {
216
+ }
217
+ }
218
+ if (contentType.includes("text/html")) {
219
+ if (format === "html") {
220
+ return redirectNote + raw + truncSuffix;
221
+ }
222
+ const text = htmlToText(raw);
223
+ return redirectNote + text + truncSuffix;
224
+ }
225
+ return redirectNote + raw + truncSuffix;
226
+ } catch (err) {
227
+ if (signal?.aborted) return "Fetch aborted by user.";
228
+ if (err instanceof Error && err.name === "AbortError") {
229
+ return `Fetch timed out after ${(timeout / 1e3).toFixed(0)}s.`;
230
+ }
231
+ return `Fetch error: ${err instanceof Error ? err.message : String(err)}`;
232
+ } finally {
233
+ clearTimeout(timer);
234
+ }
235
+ }
236
+
237
+ // src/lib/fs/pdf.ts
238
+ import fs2 from "fs/promises";
239
+ async function extractPdfText(filePath, options) {
240
+ const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
241
+ const buffer = await fs2.readFile(filePath);
242
+ const document = await pdfjs.getDocument({ data: new Uint8Array(buffer) }).promise;
243
+ const totalPages = document.numPages;
244
+ const start = Math.max(1, options?.startPage ?? 1);
245
+ const end = Math.min(totalPages, options?.endPage ?? totalPages);
246
+ const pages = [];
247
+ for (let pageNumber = start; pageNumber <= end; pageNumber += 1) {
248
+ const page = await document.getPage(pageNumber);
249
+ const content = await page.getTextContent();
250
+ const text = content.items.map((item) => "str" in item ? String(item.str) : "").join(" ").replace(/\s+/g, " ").trim();
251
+ if (text) pages.push(text);
252
+ }
253
+ return { text: pages.join("\n\n"), totalPages };
254
+ }
255
+ async function extractPdfTextFromBuffer(buffer, options) {
256
+ const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
257
+ const document = await pdfjs.getDocument({ data: buffer }).promise;
258
+ const totalPages = document.numPages;
259
+ const end = Math.min(totalPages, options?.maxPages ?? 20);
260
+ const pages = [];
261
+ for (let pageNumber = 1; pageNumber <= end; pageNumber += 1) {
262
+ const page = await document.getPage(pageNumber);
263
+ const content = await page.getTextContent();
264
+ const text = content.items.map((item) => "str" in item ? String(item.str) : "").join(" ").replace(/\s+/g, " ").trim();
265
+ if (text) pages.push(text);
266
+ }
267
+ return { text: pages.join("\n\n"), totalPages };
268
+ }
269
+
270
+ // src/lib/search/fetch-content.ts
271
+ var FETCH_TIMEOUT_MS = 15e3;
272
+ var MAX_HTML_BYTES = 512 * 1024;
273
+ var MAX_PDF_BYTES = 8 * 1024 * 1024;
274
+ var PDF_MAX_PAGES = 5;
275
+ var MIN_USEFUL_TEXT = 100;
276
+ function detectPdf(contentType, url) {
277
+ if (contentType.includes("application/pdf")) return true;
278
+ if (contentType.includes("text/html")) return false;
279
+ if (contentType.includes("text/")) return false;
280
+ try {
281
+ return new URL(url).pathname.toLowerCase().endsWith(".pdf");
282
+ } catch {
283
+ return false;
284
+ }
285
+ }
286
+ function isPdfMagicBytes(buffer) {
287
+ if (buffer.length < 5) return false;
288
+ return String.fromCharCode(buffer[0], buffer[1], buffer[2], buffer[3], buffer[4]) === "%PDF-";
289
+ }
290
+ async function parsePdfResponse(response, url) {
291
+ let buffer;
292
+ try {
293
+ const arrayBuffer = await response.arrayBuffer();
294
+ if (arrayBuffer.byteLength > MAX_PDF_BYTES) return null;
295
+ buffer = new Uint8Array(arrayBuffer);
296
+ } catch {
297
+ return null;
298
+ }
299
+ if (!isPdfMagicBytes(buffer)) {
300
+ const text = new TextDecoder().decode(buffer);
301
+ const parsed = htmlToText(text);
302
+ if (parsed.length < MIN_USEFUL_TEXT) return null;
303
+ return { text: parsed, contentType: "html", url: response.url, truncated: false };
304
+ }
305
+ try {
306
+ const result = await extractPdfTextFromBuffer(buffer, { maxPages: PDF_MAX_PAGES });
307
+ if (result.text.length < MIN_USEFUL_TEXT) return null;
308
+ return {
309
+ text: result.text,
310
+ contentType: "pdf",
311
+ url: response.url,
312
+ truncated: result.totalPages > PDF_MAX_PAGES
313
+ };
314
+ } catch {
315
+ return null;
316
+ }
317
+ }
318
+ async function parseHtmlResponse(response, url) {
319
+ const reader = response.body?.getReader();
320
+ if (!reader) return null;
321
+ const chunks = [];
322
+ let totalBytes = 0;
323
+ let truncated = false;
324
+ try {
325
+ while (true) {
326
+ const { done, value } = await reader.read();
327
+ if (done) break;
328
+ if (totalBytes + value.length > MAX_HTML_BYTES) {
329
+ chunks.push(value.subarray(0, MAX_HTML_BYTES - totalBytes));
330
+ truncated = true;
331
+ reader.cancel().catch(() => {
332
+ });
333
+ break;
334
+ }
335
+ chunks.push(value);
336
+ totalBytes += value.length;
337
+ }
338
+ } catch {
339
+ if (totalBytes === 0) return null;
340
+ }
341
+ const html = new TextDecoder().decode(
342
+ chunks.length === 1 ? chunks[0] : Buffer.concat(chunks)
343
+ );
344
+ const text = htmlToText(html);
345
+ if (text.length < MIN_USEFUL_TEXT) return null;
346
+ return { text, contentType: "html", url: response.url, truncated };
347
+ }
348
+ async function fetchAndParseContent(url) {
349
+ const controller = new AbortController();
350
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
351
+ try {
352
+ const response = await fetch(url, {
353
+ headers: {
354
+ "User-Agent": USER_AGENT,
355
+ Accept: "text/html,application/pdf,application/xhtml+xml,*/*"
356
+ },
357
+ redirect: "follow",
358
+ signal: controller.signal
359
+ });
360
+ if (!response.ok) return null;
361
+ const contentType = response.headers.get("content-type") ?? "";
362
+ if (contentType.includes("image/") || contentType.includes("audio/") || contentType.includes("video/") || contentType.includes("application/zip")) {
363
+ return null;
364
+ }
365
+ if (detectPdf(contentType, url)) {
366
+ return await parsePdfResponse(response, url);
367
+ }
368
+ return await parseHtmlResponse(response, url);
369
+ } catch {
370
+ return null;
371
+ } finally {
372
+ clearTimeout(timer);
373
+ }
374
+ }
375
+
376
+ // src/lib/search/extract.ts
377
+ var MAX_CONTENT_CHARS = 12e3;
378
+ var EXTRACTION_PROMPT = `You are a research extraction system. You receive source text and a research target.
379
+
380
+ Analyze the source through the lens of the target and extract structured findings.
381
+
382
+ Research target: {TARGET}
383
+ Source: "{TITLE}" ({URL})
384
+
385
+ Rules:
386
+ - "supports": Direct evidence, data, or arguments that SUPPORT the target. Quote or precisely paraphrase. Each item = one specific finding.
387
+ - "contradicts": Direct evidence, data, or arguments that CONTRADICT or challenge the target. Same precision.
388
+ - "related": Relevant context that neither supports nor contradicts \u2014 methodology, definitions, related phenomena, frameworks.
389
+ - "summary": One paragraph synthesizing what this source contributes to understanding the target.
390
+ - "relevanceScore": 0-10. 0 = unrelated. 5 = tangentially relevant. 10 = directly addresses the target.
391
+ - If clearly unrelated (score < 2), set supports/contradicts/related to empty arrays.
392
+ - Maximum 5 items per category. Prefer fewer precise items over many vague ones.
393
+ - Include specific numbers, methods, or datasets when available.`;
394
+ var EXTRACTION_SCHEMA = {
395
+ name: "source_extraction",
396
+ schema: {
397
+ type: "object",
398
+ properties: {
399
+ supports: {
400
+ type: "array",
401
+ items: { type: "string" },
402
+ description: "Evidence supporting the target"
403
+ },
404
+ contradicts: {
405
+ type: "array",
406
+ items: { type: "string" },
407
+ description: "Evidence contradicting the target"
408
+ },
409
+ related: {
410
+ type: "array",
411
+ items: { type: "string" },
412
+ description: "Related but non-directional findings"
413
+ },
414
+ summary: {
415
+ type: "string",
416
+ description: "One-paragraph synthesis"
417
+ },
418
+ relevanceScore: {
419
+ type: "number",
420
+ description: "0-10 relevance to target"
421
+ }
422
+ },
423
+ required: ["supports", "contradicts", "related", "summary", "relevanceScore"],
424
+ additionalProperties: false
425
+ }
426
+ };
427
+ async function extractWithTarget(input, provider) {
428
+ const truncatedContent = input.content.slice(0, MAX_CONTENT_CHARS);
429
+ const systemPrompt = EXTRACTION_PROMPT.replace("{TARGET}", input.target).replace("{TITLE}", input.title).replace("{URL}", input.url);
430
+ try {
431
+ const response = await provider.callLLM({
432
+ messages: [
433
+ { role: "system", content: systemPrompt },
434
+ { role: "user", content: truncatedContent }
435
+ ],
436
+ model: "gpt-5.4-mini",
437
+ temperature: 0,
438
+ maxTokens: 1500,
439
+ jsonSchema: EXTRACTION_SCHEMA
440
+ });
441
+ const parsed = JSON.parse(response.content);
442
+ if (typeof parsed.relevanceScore !== "number") return null;
443
+ if (!Array.isArray(parsed.supports)) return null;
444
+ return parsed;
445
+ } catch {
446
+ return null;
447
+ }
448
+ }
449
+ async function extractBatch(inputs, provider) {
450
+ const results = await Promise.allSettled(
451
+ inputs.map(async (input) => {
452
+ const result = await extractWithTarget(input, provider);
453
+ return [input.url, result];
454
+ })
455
+ );
456
+ const map = /* @__PURE__ */ new Map();
457
+ for (const result of results) {
458
+ if (result.status === "fulfilled" && result.value[1] !== null) {
459
+ map.set(result.value[0], result.value[1]);
460
+ }
461
+ }
462
+ return map;
463
+ }
464
+ function formatExtractionResults(sources) {
465
+ if (sources.length === 0) return "No relevant results found for this target.";
466
+ const sorted = [...sources].sort((a, b) => b.extraction.relevanceScore - a.extraction.relevanceScore);
467
+ const parts = [`Based on ${sorted.length} source${sorted.length !== 1 ? "s" : ""}:
468
+ `];
469
+ for (let i = 0; i < sorted.length; i++) {
470
+ const s = sorted[i];
471
+ const providerLabel = s.provider ? ` [${s.provider}]` : "";
472
+ parts.push(`${i + 1}. "${s.title}"${providerLabel} (relevance: ${s.extraction.relevanceScore}/10)`);
473
+ parts.push(` ${s.url}`);
474
+ if (s.extraction.supports.length > 0) {
475
+ parts.push(` Supports:`);
476
+ for (const item of s.extraction.supports) {
477
+ parts.push(` + ${item}`);
478
+ }
479
+ }
480
+ if (s.extraction.contradicts.length > 0) {
481
+ parts.push(` Contradicts:`);
482
+ for (const item of s.extraction.contradicts) {
483
+ parts.push(` - ${item}`);
484
+ }
485
+ }
486
+ if (s.extraction.related.length > 0) {
487
+ parts.push(` Related:`);
488
+ for (const item of s.extraction.related) {
489
+ parts.push(` ~ ${item}`);
490
+ }
491
+ }
492
+ parts.push(` Summary: ${s.extraction.summary}`);
493
+ parts.push("");
494
+ }
495
+ return parts.join("\n");
496
+ }
497
+
498
+ export {
499
+ readJsonFile,
500
+ writeJsonFile,
501
+ extractPdfText,
502
+ themeValues,
503
+ getConfiguredOpenAIApiKey,
504
+ getSemanticScholarApiKey,
505
+ getOpenAlexApiKey,
506
+ getBraveApiKey,
507
+ loadOpenResearchConfig,
508
+ saveOpenResearchConfig,
509
+ ensureOpenResearchConfig,
510
+ USER_AGENT,
511
+ executeFetchUrl,
512
+ fetchAndParseContent,
513
+ extractBatch,
514
+ formatExtractionResults
515
+ };
@@ -1,47 +1,14 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
1
+ import {
2
+ getWorkspaceSessionsDir
3
+ } from "./chunk-I5NVYKG7.js";
7
4
 
8
5
  // src/lib/workspace/sessions.ts
9
6
  import fs from "fs/promises";
10
- import path2 from "path";
11
-
12
- // src/lib/fs/paths.ts
13
- import os from "os";
14
7
  import path from "path";
15
- function resolveHomeDir(options) {
16
- return options?.homeDir ?? os.homedir();
17
- }
18
- function getOpenResearchRoot(options) {
19
- return path.join(resolveHomeDir(options), ".open-research");
20
- }
21
- function getOpenResearchAuthFile(options) {
22
- return path.join(getOpenResearchRoot(options), "auth.json");
23
- }
24
- function getOpenResearchConfigFile(options) {
25
- return path.join(getOpenResearchRoot(options), "config.json");
26
- }
27
- function getOpenResearchSkillsDir(options) {
28
- return path.join(getOpenResearchRoot(options), "skills");
29
- }
30
- function getWorkspaceMetaDir(workspaceDir) {
31
- return path.join(workspaceDir, ".open-research");
32
- }
33
- function getWorkspaceProjectFile(workspaceDir) {
34
- return path.join(getWorkspaceMetaDir(workspaceDir), "project.json");
35
- }
36
- function getWorkspaceSessionsDir(workspaceDir) {
37
- return path.join(getWorkspaceMetaDir(workspaceDir), "sessions");
38
- }
39
-
40
- // src/lib/workspace/sessions.ts
41
8
  async function appendSessionEvent(workspaceDir, sessionId, event) {
42
9
  const sessionsDir = getWorkspaceSessionsDir(workspaceDir);
43
10
  await fs.mkdir(sessionsDir, { recursive: true });
44
- const sessionFile = path2.join(sessionsDir, `${sessionId}.jsonl`);
11
+ const sessionFile = path.join(sessionsDir, `${sessionId}.jsonl`);
45
12
  await fs.appendFile(sessionFile, `${JSON.stringify(event)}
46
13
  `, "utf8");
47
14
  }
@@ -66,7 +33,7 @@ async function listSessions(workspaceDir) {
66
33
  for (const file of files) {
67
34
  if (!file.endsWith(".jsonl")) continue;
68
35
  const id = file.replace(/\.jsonl$/, "");
69
- const raw = await fs.readFile(path2.join(sessionsDir, file), "utf8");
36
+ const raw = await fs.readFile(path.join(sessionsDir, file), "utf8");
70
37
  const events = parseEvents(raw);
71
38
  if (events.length === 0) continue;
72
39
  const chatTurns = events.filter((e) => e.type === "chat.turn");
@@ -87,7 +54,7 @@ async function listSessions(workspaceDir) {
87
54
  }
88
55
  async function loadSessionHistory(workspaceDir, sessionId) {
89
56
  const sessionsDir = getWorkspaceSessionsDir(workspaceDir);
90
- const sessionFile = path2.join(sessionsDir, `${sessionId}.jsonl`);
57
+ const sessionFile = path.join(sessionsDir, `${sessionId}.jsonl`);
91
58
  const raw = await fs.readFile(sessionFile, "utf8");
92
59
  const events = parseEvents(raw);
93
60
  const messages = [];
@@ -114,14 +81,6 @@ async function loadSessionHistory(workspaceDir, sessionId) {
114
81
  }
115
82
 
116
83
  export {
117
- __require,
118
- getOpenResearchRoot,
119
- getOpenResearchAuthFile,
120
- getOpenResearchConfigFile,
121
- getOpenResearchSkillsDir,
122
- getWorkspaceMetaDir,
123
- getWorkspaceProjectFile,
124
- getWorkspaceSessionsDir,
125
84
  appendSessionEvent,
126
85
  listSessions,
127
86
  loadSessionHistory