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.
- package/README.md +144 -85
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3WM33M3O.js +38 -0
- package/dist/chunk-I5NVYKG7.js +37 -0
- package/dist/chunk-IOR7G25X.js +215 -0
- package/dist/chunk-KJHM7ZW2.js +15 -0
- package/dist/chunk-TQSQRNX6.js +515 -0
- package/dist/{chunk-AYB7CAO5.js → chunk-ZUSIRA5S.js} +6 -47
- package/dist/cli.js +528 -452
- package/dist/manager-queue-F4VVZMTE.js +608 -0
- package/dist/query-agent-LRUUJR4F.js +193 -0
- package/dist/read-tools-GHBKBZFE.js +13 -0
- package/dist/relevance-agent-CCN7JGTM.js +74 -0
- package/dist/scaffolding-MSAICMWV.js +90 -0
- package/dist/{sessions-FMB5GHSR.js → sessions-GRES2MUV.js} +3 -1
- package/dist/status-GEEAGLPF.js +120 -0
- package/dist/store-LT5EGDOI.js +13 -0
- package/dist/web-search-B7D5WMHU.js +177 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,64 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
__require,
|
|
4
3
|
appendSessionEvent,
|
|
4
|
+
loadSessionHistory
|
|
5
|
+
} from "./chunk-ZUSIRA5S.js";
|
|
6
|
+
import {
|
|
7
|
+
ensureOpenResearchConfig,
|
|
8
|
+
executeFetchUrl,
|
|
9
|
+
extractBatch,
|
|
10
|
+
extractPdfText,
|
|
11
|
+
fetchAndParseContent,
|
|
12
|
+
formatExtractionResults,
|
|
13
|
+
getBraveApiKey,
|
|
14
|
+
getConfiguredOpenAIApiKey,
|
|
15
|
+
getOpenAlexApiKey,
|
|
16
|
+
getSemanticScholarApiKey,
|
|
17
|
+
loadOpenResearchConfig,
|
|
18
|
+
readJsonFile,
|
|
19
|
+
saveOpenResearchConfig,
|
|
20
|
+
themeValues,
|
|
21
|
+
writeJsonFile
|
|
22
|
+
} from "./chunk-TQSQRNX6.js";
|
|
23
|
+
import {
|
|
5
24
|
getOpenResearchAuthFile,
|
|
6
|
-
getOpenResearchConfigFile,
|
|
7
25
|
getOpenResearchRoot,
|
|
8
26
|
getOpenResearchSkillsDir,
|
|
9
27
|
getWorkspaceMetaDir,
|
|
10
28
|
getWorkspaceProjectFile,
|
|
11
|
-
getWorkspaceSessionsDir
|
|
12
|
-
|
|
13
|
-
|
|
29
|
+
getWorkspaceSessionsDir
|
|
30
|
+
} from "./chunk-I5NVYKG7.js";
|
|
31
|
+
import {
|
|
32
|
+
__require
|
|
33
|
+
} from "./chunk-3RG5ZIWI.js";
|
|
14
34
|
|
|
15
35
|
// src/cli.ts
|
|
16
36
|
import React6 from "react";
|
|
17
|
-
import
|
|
37
|
+
import path19 from "path";
|
|
18
38
|
import { Command } from "commander";
|
|
19
39
|
import { render } from "ink";
|
|
20
40
|
|
|
21
41
|
// src/lib/workspace/project.ts
|
|
22
|
-
import fs2 from "fs/promises";
|
|
23
|
-
import path2 from "path";
|
|
24
|
-
|
|
25
|
-
// src/lib/fs/json.ts
|
|
26
42
|
import fs from "fs/promises";
|
|
27
43
|
import path from "path";
|
|
28
|
-
async function readJsonFile(filePath, fallback) {
|
|
29
|
-
try {
|
|
30
|
-
const raw = await fs.readFile(filePath, "utf8");
|
|
31
|
-
return JSON.parse(raw);
|
|
32
|
-
} catch (error) {
|
|
33
|
-
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
34
|
-
if (code === "ENOENT") {
|
|
35
|
-
return fallback;
|
|
36
|
-
}
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
async function writeJsonFile(filePath, value, mode) {
|
|
41
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
42
|
-
await fs.writeFile(filePath, JSON.stringify(value, null, 2), {
|
|
43
|
-
encoding: "utf8",
|
|
44
|
-
mode
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// src/lib/workspace/project.ts
|
|
49
44
|
var MANAGED_DIRS = ["sources", "notes", "artifacts", "papers", "experiments"];
|
|
50
45
|
async function initWorkspace(options) {
|
|
51
|
-
const workspaceDir =
|
|
52
|
-
await
|
|
46
|
+
const workspaceDir = path.resolve(options.workspaceDir);
|
|
47
|
+
await fs.mkdir(workspaceDir, { recursive: true });
|
|
53
48
|
for (const dir of MANAGED_DIRS) {
|
|
54
|
-
await
|
|
49
|
+
await fs.mkdir(path.join(workspaceDir, dir), { recursive: true });
|
|
55
50
|
}
|
|
56
|
-
await
|
|
57
|
-
await
|
|
51
|
+
await fs.mkdir(getWorkspaceMetaDir(workspaceDir), { recursive: true });
|
|
52
|
+
await fs.mkdir(getWorkspaceSessionsDir(workspaceDir), { recursive: true });
|
|
58
53
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
59
54
|
const project = {
|
|
60
55
|
version: 1,
|
|
61
|
-
title: options.title?.trim() ||
|
|
56
|
+
title: options.title?.trim() || path.basename(workspaceDir),
|
|
62
57
|
createdAt: timestamp,
|
|
63
58
|
updatedAt: timestamp,
|
|
64
59
|
defaults: {
|
|
@@ -74,36 +69,15 @@ async function initWorkspace(options) {
|
|
|
74
69
|
}
|
|
75
70
|
async function loadWorkspaceProject(workspaceDir) {
|
|
76
71
|
return readJsonFile(
|
|
77
|
-
getWorkspaceProjectFile(
|
|
72
|
+
getWorkspaceProjectFile(path.resolve(workspaceDir)),
|
|
78
73
|
null
|
|
79
74
|
);
|
|
80
75
|
}
|
|
81
76
|
|
|
82
77
|
// src/lib/workspace/sources.ts
|
|
83
|
-
import
|
|
84
|
-
import
|
|
78
|
+
import fs2 from "fs/promises";
|
|
79
|
+
import path2 from "path";
|
|
85
80
|
import { load } from "cheerio";
|
|
86
|
-
|
|
87
|
-
// src/lib/fs/pdf.ts
|
|
88
|
-
import fs3 from "fs/promises";
|
|
89
|
-
async function extractPdfText(filePath, options) {
|
|
90
|
-
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
91
|
-
const buffer = await fs3.readFile(filePath);
|
|
92
|
-
const document = await pdfjs.getDocument({ data: new Uint8Array(buffer) }).promise;
|
|
93
|
-
const totalPages = document.numPages;
|
|
94
|
-
const start = Math.max(1, options?.startPage ?? 1);
|
|
95
|
-
const end = Math.min(totalPages, options?.endPage ?? totalPages);
|
|
96
|
-
const pages = [];
|
|
97
|
-
for (let pageNumber = start; pageNumber <= end; pageNumber += 1) {
|
|
98
|
-
const page = await document.getPage(pageNumber);
|
|
99
|
-
const content = await page.getTextContent();
|
|
100
|
-
const text = content.items.map((item) => "str" in item ? String(item.str) : "").join(" ").replace(/\s+/g, " ").trim();
|
|
101
|
-
if (text) pages.push(text);
|
|
102
|
-
}
|
|
103
|
-
return { text: pages.join("\n\n"), totalPages };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// src/lib/workspace/sources.ts
|
|
107
81
|
function slugify(value) {
|
|
108
82
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "source";
|
|
109
83
|
}
|
|
@@ -138,10 +112,10 @@ async function addUrlSource(input2) {
|
|
|
138
112
|
const titleMatch = markdown.match(/^#\s+(.+)$/m);
|
|
139
113
|
const label = titleMatch?.[1]?.trim() || new URL(input2.url).hostname;
|
|
140
114
|
const fileName = `${slugify(label)}.md`;
|
|
141
|
-
const relativePath =
|
|
142
|
-
const absolutePath =
|
|
143
|
-
await
|
|
144
|
-
await
|
|
115
|
+
const relativePath = path2.join("sources", fileName);
|
|
116
|
+
const absolutePath = path2.join(input2.workspaceDir, relativePath);
|
|
117
|
+
await fs2.mkdir(path2.dirname(absolutePath), { recursive: true });
|
|
118
|
+
await fs2.writeFile(absolutePath, markdown, "utf8");
|
|
145
119
|
const source2 = {
|
|
146
120
|
id: crypto.randomUUID(),
|
|
147
121
|
kind: "url",
|
|
@@ -153,31 +127,31 @@ async function addUrlSource(input2) {
|
|
|
153
127
|
return source2;
|
|
154
128
|
}
|
|
155
129
|
async function addFileSource(input2) {
|
|
156
|
-
const absoluteInput =
|
|
157
|
-
const parsed =
|
|
130
|
+
const absoluteInput = path2.resolve(input2.filePath);
|
|
131
|
+
const parsed = path2.parse(absoluteInput);
|
|
158
132
|
const slug = slugify(parsed.name);
|
|
159
|
-
const markdownRelative =
|
|
160
|
-
const markdownAbsolute =
|
|
133
|
+
const markdownRelative = path2.join("sources", `${slug}.md`);
|
|
134
|
+
const markdownAbsolute = path2.join(input2.workspaceDir, markdownRelative);
|
|
161
135
|
let markdown = "";
|
|
162
136
|
if (parsed.ext.toLowerCase() === ".pdf") {
|
|
163
|
-
const rawRelative =
|
|
164
|
-
const rawAbsolute =
|
|
165
|
-
await
|
|
166
|
-
await
|
|
137
|
+
const rawRelative = path2.join("sources", `${slug}.pdf`);
|
|
138
|
+
const rawAbsolute = path2.join(input2.workspaceDir, rawRelative);
|
|
139
|
+
await fs2.mkdir(path2.dirname(rawAbsolute), { recursive: true });
|
|
140
|
+
await fs2.copyFile(absoluteInput, rawAbsolute);
|
|
167
141
|
const { text } = await extractPdfText(absoluteInput);
|
|
168
142
|
markdown = `# ${parsed.name}
|
|
169
143
|
|
|
170
144
|
${text}`;
|
|
171
145
|
} else {
|
|
172
|
-
markdown = await
|
|
146
|
+
markdown = await fs2.readFile(absoluteInput, "utf8");
|
|
173
147
|
if (!markdown.startsWith("# ")) {
|
|
174
148
|
markdown = `# ${parsed.name}
|
|
175
149
|
|
|
176
150
|
${markdown}`;
|
|
177
151
|
}
|
|
178
152
|
}
|
|
179
|
-
await
|
|
180
|
-
await
|
|
153
|
+
await fs2.mkdir(path2.dirname(markdownAbsolute), { recursive: true });
|
|
154
|
+
await fs2.writeFile(markdownAbsolute, markdown, "utf8");
|
|
181
155
|
const source2 = {
|
|
182
156
|
id: crypto.randomUUID(),
|
|
183
157
|
kind: parsed.ext.toLowerCase() === ".pdf" ? "pdf" : "file",
|
|
@@ -190,8 +164,8 @@ ${markdown}`;
|
|
|
190
164
|
}
|
|
191
165
|
|
|
192
166
|
// src/lib/auth/import-codex.ts
|
|
193
|
-
import
|
|
194
|
-
import
|
|
167
|
+
import fs4 from "fs/promises";
|
|
168
|
+
import path3 from "path";
|
|
195
169
|
|
|
196
170
|
// src/lib/storage/credential-types.ts
|
|
197
171
|
function getDefaultCredentialCapabilities() {
|
|
@@ -220,18 +194,18 @@ function getBootstrapCredentialValidation() {
|
|
|
220
194
|
}
|
|
221
195
|
|
|
222
196
|
// src/lib/auth/store.ts
|
|
223
|
-
import
|
|
197
|
+
import fs3 from "fs/promises";
|
|
224
198
|
var AUTH_FILE_MODE = 384;
|
|
225
199
|
async function ensureCliHome(options) {
|
|
226
200
|
const root = getOpenResearchRoot(options);
|
|
227
|
-
await
|
|
201
|
+
await fs3.mkdir(root, { recursive: true, mode: 448 });
|
|
228
202
|
return root;
|
|
229
203
|
}
|
|
230
204
|
async function saveStoredAuth(auth2, options) {
|
|
231
205
|
await ensureCliHome(options);
|
|
232
206
|
const authFile = getOpenResearchAuthFile(options);
|
|
233
207
|
await writeJsonFile(authFile, auth2, AUTH_FILE_MODE);
|
|
234
|
-
await
|
|
208
|
+
await fs3.chmod(authFile, AUTH_FILE_MODE);
|
|
235
209
|
return authFile;
|
|
236
210
|
}
|
|
237
211
|
async function loadStoredAuth(options) {
|
|
@@ -240,7 +214,7 @@ async function loadStoredAuth(options) {
|
|
|
240
214
|
}
|
|
241
215
|
async function clearStoredAuth(options) {
|
|
242
216
|
const authFile = getOpenResearchAuthFile(options);
|
|
243
|
-
await
|
|
217
|
+
await fs3.rm(authFile, { force: true });
|
|
244
218
|
}
|
|
245
219
|
|
|
246
220
|
// src/lib/auth/openai-oauth.ts
|
|
@@ -333,9 +307,9 @@ function getTokenExpiryMs(token) {
|
|
|
333
307
|
}
|
|
334
308
|
async function importCodexAuth(options = {}) {
|
|
335
309
|
const now = options.now ?? Date.now;
|
|
336
|
-
const codexAuthPath = options.codexAuthFilePath ??
|
|
310
|
+
const codexAuthPath = options.codexAuthFilePath ?? path3.join(options.homeDir ?? process.env.HOME ?? "", ".codex", "auth.json");
|
|
337
311
|
const parsed = JSON.parse(
|
|
338
|
-
await
|
|
312
|
+
await fs4.readFile(codexAuthPath, "utf8")
|
|
339
313
|
);
|
|
340
314
|
if (parsed.auth_mode !== "chatgpt") {
|
|
341
315
|
throw new Error("Codex is not signed in with OpenAI on this device.");
|
|
@@ -666,73 +640,6 @@ async function validateOpenAIConnection(input2) {
|
|
|
666
640
|
}
|
|
667
641
|
}
|
|
668
642
|
|
|
669
|
-
// src/lib/config/store.ts
|
|
670
|
-
import { z } from "zod";
|
|
671
|
-
var themeValues = ["dark", "light"];
|
|
672
|
-
var openAIProviderConfigSchema = z.object({
|
|
673
|
-
apiKey: z.string().optional()
|
|
674
|
-
}).optional();
|
|
675
|
-
var openResearchConfigSchema = z.object({
|
|
676
|
-
version: z.literal(1),
|
|
677
|
-
defaults: z.object({
|
|
678
|
-
model: z.string().min(1),
|
|
679
|
-
reasoningEffort: z.enum(["low", "medium", "high", "xhigh"]),
|
|
680
|
-
editPolicy: z.literal("mixed")
|
|
681
|
-
}),
|
|
682
|
-
theme: z.enum(themeValues).default("dark"),
|
|
683
|
-
lastWorkspace: z.string().nullable(),
|
|
684
|
-
providers: z.object({
|
|
685
|
-
openai: openAIProviderConfigSchema
|
|
686
|
-
}).optional(),
|
|
687
|
-
apiKeys: z.object({
|
|
688
|
-
openai: z.string().optional(),
|
|
689
|
-
semanticScholar: z.string().optional(),
|
|
690
|
-
openAlex: z.string().optional()
|
|
691
|
-
}).optional()
|
|
692
|
-
});
|
|
693
|
-
var DEFAULT_OPEN_RESEARCH_CONFIG = {
|
|
694
|
-
version: 1,
|
|
695
|
-
defaults: {
|
|
696
|
-
model: "gpt-5.4",
|
|
697
|
-
reasoningEffort: "medium",
|
|
698
|
-
editPolicy: "mixed"
|
|
699
|
-
},
|
|
700
|
-
theme: "dark",
|
|
701
|
-
lastWorkspace: null,
|
|
702
|
-
providers: {
|
|
703
|
-
openai: {}
|
|
704
|
-
},
|
|
705
|
-
apiKeys: {}
|
|
706
|
-
};
|
|
707
|
-
function getConfiguredOpenAIApiKey(config) {
|
|
708
|
-
return config?.providers?.openai?.apiKey || config?.apiKeys?.openai;
|
|
709
|
-
}
|
|
710
|
-
function getSemanticScholarApiKey(config) {
|
|
711
|
-
return config?.apiKeys?.semanticScholar || process.env.SEMANTIC_SCHOLAR_API_KEY;
|
|
712
|
-
}
|
|
713
|
-
function getOpenAlexApiKey(config) {
|
|
714
|
-
return config?.apiKeys?.openAlex || process.env.OPENALEX_API_KEY;
|
|
715
|
-
}
|
|
716
|
-
async function loadOpenResearchConfig(options) {
|
|
717
|
-
const configFile = getOpenResearchConfigFile(options);
|
|
718
|
-
const config = await readJsonFile(configFile, null);
|
|
719
|
-
if (!config) {
|
|
720
|
-
return null;
|
|
721
|
-
}
|
|
722
|
-
return openResearchConfigSchema.parse(config);
|
|
723
|
-
}
|
|
724
|
-
async function saveOpenResearchConfig(config, options) {
|
|
725
|
-
await writeJsonFile(getOpenResearchConfigFile(options), config);
|
|
726
|
-
}
|
|
727
|
-
async function ensureOpenResearchConfig(options) {
|
|
728
|
-
const existing = await loadOpenResearchConfig(options);
|
|
729
|
-
if (existing) {
|
|
730
|
-
return existing;
|
|
731
|
-
}
|
|
732
|
-
await writeJsonFile(getOpenResearchConfigFile(options), DEFAULT_OPEN_RESEARCH_CONFIG);
|
|
733
|
-
return DEFAULT_OPEN_RESEARCH_CONFIG;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
643
|
// src/lib/llm/provider-resolution.ts
|
|
737
644
|
function trimCredential(value) {
|
|
738
645
|
const trimmed = value?.trim();
|
|
@@ -836,26 +743,26 @@ async function getAuthStatus(options) {
|
|
|
836
743
|
}
|
|
837
744
|
|
|
838
745
|
// src/lib/skills/registry.ts
|
|
839
|
-
import
|
|
746
|
+
import fs5 from "fs/promises";
|
|
840
747
|
import fsSync from "fs";
|
|
841
|
-
import
|
|
748
|
+
import path4 from "path";
|
|
842
749
|
import matter from "gray-matter";
|
|
843
750
|
var BUILTIN_SKILLS_DIR = [
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
].find((candidate) => fsSync.existsSync(candidate)) ??
|
|
751
|
+
path4.resolve(path4.join(import.meta.dirname, "../../../builtin-skills")),
|
|
752
|
+
path4.resolve(path4.join(import.meta.dirname, "../builtin-skills"))
|
|
753
|
+
].find((candidate) => fsSync.existsSync(candidate)) ?? path4.resolve(path4.join(import.meta.dirname, "../../../builtin-skills"));
|
|
847
754
|
function normalizeSkillName(name) {
|
|
848
755
|
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
849
756
|
}
|
|
850
757
|
async function ensureUserSkillsDir(options) {
|
|
851
758
|
const dir = getOpenResearchSkillsDir(options);
|
|
852
|
-
await
|
|
759
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
853
760
|
return dir;
|
|
854
761
|
}
|
|
855
762
|
async function readSkillSummary(skillDir, source2) {
|
|
856
|
-
const skillFile =
|
|
763
|
+
const skillFile = path4.join(skillDir, "SKILL.md");
|
|
857
764
|
try {
|
|
858
|
-
const raw = await
|
|
765
|
+
const raw = await fs5.readFile(skillFile, "utf8");
|
|
859
766
|
const parsed = matter(raw);
|
|
860
767
|
const name = String(parsed.data.name ?? "").trim();
|
|
861
768
|
const description = String(parsed.data.description ?? "").trim();
|
|
@@ -874,9 +781,9 @@ async function readSkillSummary(skillDir, source2) {
|
|
|
874
781
|
}
|
|
875
782
|
}
|
|
876
783
|
async function listSkillsInDirectory(rootDir, source2) {
|
|
877
|
-
const entries = await
|
|
784
|
+
const entries = await fs5.readdir(rootDir, { withFileTypes: true }).catch(() => []);
|
|
878
785
|
const results = await Promise.all(
|
|
879
|
-
entries.filter((entry) => entry.isDirectory()).map((entry) => readSkillSummary(
|
|
786
|
+
entries.filter((entry) => entry.isDirectory()).map((entry) => readSkillSummary(path4.join(rootDir, entry.name), source2))
|
|
880
787
|
);
|
|
881
788
|
return results.filter((value) => Boolean(value));
|
|
882
789
|
}
|
|
@@ -890,15 +797,15 @@ async function listAvailableSkills(options) {
|
|
|
890
797
|
}
|
|
891
798
|
async function validateSkillDirectory(input2) {
|
|
892
799
|
const errors = [];
|
|
893
|
-
const skillFile =
|
|
894
|
-
const raw = await
|
|
800
|
+
const skillFile = path4.join(input2.skillDir, "SKILL.md");
|
|
801
|
+
const raw = await fs5.readFile(skillFile, "utf8").catch(() => "");
|
|
895
802
|
if (!raw) {
|
|
896
803
|
return { ok: false, errors: ["SKILL.md is missing."] };
|
|
897
804
|
}
|
|
898
805
|
const parsed = matter(raw);
|
|
899
806
|
const name = String(parsed.data.name ?? "").trim();
|
|
900
807
|
const description = String(parsed.data.description ?? "").trim();
|
|
901
|
-
const normalizedDirName =
|
|
808
|
+
const normalizedDirName = path4.basename(input2.skillDir);
|
|
902
809
|
const normalizedName = normalizeSkillName(name);
|
|
903
810
|
if (!name) {
|
|
904
811
|
errors.push("Skill frontmatter requires a name.");
|
|
@@ -921,10 +828,10 @@ async function validateSkillDirectory(input2) {
|
|
|
921
828
|
async function createSkillScaffold(input2) {
|
|
922
829
|
const skillsDir = await ensureUserSkillsDir({ homeDir: input2.homeDir });
|
|
923
830
|
const name = normalizeSkillName(input2.name);
|
|
924
|
-
const skillDir =
|
|
925
|
-
await
|
|
926
|
-
await
|
|
927
|
-
await
|
|
831
|
+
const skillDir = path4.join(skillsDir, name);
|
|
832
|
+
await fs5.mkdir(path4.join(skillDir, "scripts"), { recursive: true });
|
|
833
|
+
await fs5.mkdir(path4.join(skillDir, "references"), { recursive: true });
|
|
834
|
+
await fs5.mkdir(path4.join(skillDir, "assets"), { recursive: true });
|
|
928
835
|
const body = `---
|
|
929
836
|
name: ${name}
|
|
930
837
|
description: ${input2.description}
|
|
@@ -944,7 +851,7 @@ ${input2.examples.map((example) => `- ${example}`).join("\n")}
|
|
|
944
851
|
|
|
945
852
|
${input2.workflow}
|
|
946
853
|
`;
|
|
947
|
-
await
|
|
854
|
+
await fs5.writeFile(path4.join(skillDir, "SKILL.md"), body, "utf8");
|
|
948
855
|
return skillDir;
|
|
949
856
|
}
|
|
950
857
|
|
|
@@ -982,13 +889,13 @@ function formatDateTime(value) {
|
|
|
982
889
|
}
|
|
983
890
|
|
|
984
891
|
// src/lib/cli/version.ts
|
|
985
|
-
var PACKAGE_VERSION = "0.
|
|
892
|
+
var PACKAGE_VERSION = "1.0.0";
|
|
986
893
|
function getPackageVersion() {
|
|
987
894
|
return PACKAGE_VERSION;
|
|
988
895
|
}
|
|
989
896
|
|
|
990
897
|
// src/tui/app.tsx
|
|
991
|
-
import
|
|
898
|
+
import path18 from "path";
|
|
992
899
|
import {
|
|
993
900
|
startTransition as startTransition2,
|
|
994
901
|
useDeferredValue,
|
|
@@ -2448,8 +2355,8 @@ function useTheme() {
|
|
|
2448
2355
|
}
|
|
2449
2356
|
|
|
2450
2357
|
// src/lib/workspace/scan.ts
|
|
2451
|
-
import
|
|
2452
|
-
import
|
|
2358
|
+
import fs6 from "fs/promises";
|
|
2359
|
+
import path5 from "path";
|
|
2453
2360
|
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2454
2361
|
".md",
|
|
2455
2362
|
".markdown",
|
|
@@ -2479,30 +2386,30 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
2479
2386
|
".turbo"
|
|
2480
2387
|
]);
|
|
2481
2388
|
async function walkDir(rootDir, currentDir, out) {
|
|
2482
|
-
const entries = await
|
|
2389
|
+
const entries = await fs6.readdir(currentDir, { withFileTypes: true });
|
|
2483
2390
|
for (const entry of entries) {
|
|
2484
2391
|
if (IGNORED_DIRS.has(entry.name)) {
|
|
2485
2392
|
continue;
|
|
2486
2393
|
}
|
|
2487
|
-
const fullPath =
|
|
2488
|
-
const relativePath =
|
|
2394
|
+
const fullPath = path5.join(currentDir, entry.name);
|
|
2395
|
+
const relativePath = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
2489
2396
|
if (entry.isDirectory()) {
|
|
2490
2397
|
await walkDir(rootDir, fullPath, out);
|
|
2491
2398
|
continue;
|
|
2492
2399
|
}
|
|
2493
|
-
if (!TEXT_EXTENSIONS.has(
|
|
2400
|
+
if (!TEXT_EXTENSIONS.has(path5.extname(entry.name).toLowerCase())) {
|
|
2494
2401
|
continue;
|
|
2495
2402
|
}
|
|
2496
2403
|
out.push({
|
|
2497
2404
|
key: `path:${relativePath}`,
|
|
2498
2405
|
label: relativePath,
|
|
2499
2406
|
path: relativePath,
|
|
2500
|
-
content: await
|
|
2407
|
+
content: await fs6.readFile(fullPath, "utf8")
|
|
2501
2408
|
});
|
|
2502
2409
|
}
|
|
2503
2410
|
}
|
|
2504
2411
|
async function scanWorkspace(workspaceDir) {
|
|
2505
|
-
const resolved =
|
|
2412
|
+
const resolved = path5.resolve(workspaceDir);
|
|
2506
2413
|
const project = await loadWorkspaceProject(resolved);
|
|
2507
2414
|
if (!project) {
|
|
2508
2415
|
throw new Error("Not an Open Research workspace. Run `open-research init` first.");
|
|
@@ -3408,10 +3315,14 @@ var TOOL_SCHEMAS = [
|
|
|
3408
3315
|
type: "function",
|
|
3409
3316
|
function: {
|
|
3410
3317
|
name: "search_external_sources",
|
|
3411
|
-
description: "Search OpenAlex, Semantic Scholar, and arXiv for academic papers
|
|
3318
|
+
description: "Search OpenAlex, Semantic Scholar, and arXiv for academic papers, then fetch content (PDFs, abstracts) and extract structured findings relative to the target. Returns supports/contradicts/related evidence for each source.",
|
|
3412
3319
|
parameters: {
|
|
3413
3320
|
type: "object",
|
|
3414
3321
|
properties: {
|
|
3322
|
+
target: {
|
|
3323
|
+
type: "string",
|
|
3324
|
+
description: "The research claim, hypothesis, or question to evaluate sources against. Each paper will be analyzed for evidence that supports, contradicts, or relates to this target. Be specific: 'What speedups do efficient attention methods achieve' not 'attention'."
|
|
3325
|
+
},
|
|
3415
3326
|
searches: {
|
|
3416
3327
|
type: "array",
|
|
3417
3328
|
items: {
|
|
@@ -3430,7 +3341,33 @@ var TOOL_SCHEMAS = [
|
|
|
3430
3341
|
description: "Maximum number of results to return. Default: 8."
|
|
3431
3342
|
}
|
|
3432
3343
|
},
|
|
3433
|
-
required: ["searches"],
|
|
3344
|
+
required: ["target", "searches"],
|
|
3345
|
+
additionalProperties: false
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
},
|
|
3349
|
+
{
|
|
3350
|
+
type: "function",
|
|
3351
|
+
function: {
|
|
3352
|
+
name: "web_search",
|
|
3353
|
+
description: "Search the web via DuckDuckGo, fetch top results, and extract structured findings relative to the target. Use for non-academic sources: documentation, blog posts, datasets, reports, news. For academic papers, use search_external_sources.",
|
|
3354
|
+
parameters: {
|
|
3355
|
+
type: "object",
|
|
3356
|
+
properties: {
|
|
3357
|
+
target: {
|
|
3358
|
+
type: "string",
|
|
3359
|
+
description: "The research claim or question to evaluate results against. Be specific: 'How to configure num_workers in PyTorch DataLoader for multi-GPU' not 'PyTorch'."
|
|
3360
|
+
},
|
|
3361
|
+
query: {
|
|
3362
|
+
type: "string",
|
|
3363
|
+
description: "The web search query."
|
|
3364
|
+
},
|
|
3365
|
+
num_results: {
|
|
3366
|
+
type: "number",
|
|
3367
|
+
description: "Maximum pages to fetch and analyze. Default: 5, max: 8."
|
|
3368
|
+
}
|
|
3369
|
+
},
|
|
3370
|
+
required: ["target", "query"],
|
|
3434
3371
|
additionalProperties: false
|
|
3435
3372
|
}
|
|
3436
3373
|
}
|
|
@@ -3602,6 +3539,42 @@ var TOOL_SCHEMAS = [
|
|
|
3602
3539
|
additionalProperties: false
|
|
3603
3540
|
}
|
|
3604
3541
|
}
|
|
3542
|
+
},
|
|
3543
|
+
// ── Ontology ──────────────────────────────────────────────────────────
|
|
3544
|
+
{
|
|
3545
|
+
type: "function",
|
|
3546
|
+
function: {
|
|
3547
|
+
name: "query_ontology",
|
|
3548
|
+
description: "Ask a question about your research knowledge. A query agent traverses the project's ontology (sources, findings, claims, contradictions, evidence chains) and returns a synthesized answer. Use for: finding evidence for/against a claim, checking what contradicts something, getting methodology details, or understanding how findings connect. Returns a natural language answer \u2014 not raw data.",
|
|
3549
|
+
parameters: {
|
|
3550
|
+
type: "object",
|
|
3551
|
+
properties: {
|
|
3552
|
+
query: {
|
|
3553
|
+
type: "string",
|
|
3554
|
+
description: "Natural language research question. Be specific. Good: 'what evidence contradicts the transformer efficiency claim?' Good: 'what methods were used across the scaling studies?' Bad: 'tell me about transformers' (too vague)"
|
|
3555
|
+
},
|
|
3556
|
+
scope: {
|
|
3557
|
+
type: "string",
|
|
3558
|
+
enum: ["claims", "sources", "questions", "methods", "findings", "insights"],
|
|
3559
|
+
description: "Narrow the search to a specific note kind. Omit to search everything."
|
|
3560
|
+
}
|
|
3561
|
+
},
|
|
3562
|
+
required: ["query"],
|
|
3563
|
+
additionalProperties: false
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
},
|
|
3567
|
+
{
|
|
3568
|
+
type: "function",
|
|
3569
|
+
function: {
|
|
3570
|
+
name: "ontology_status",
|
|
3571
|
+
description: "Get a snapshot of the research ontology: how many sources, findings, claims, methods, questions, and insights have been captured. Also shows contradiction count, unsupported claims, and open questions. Use to assess coverage, identify gaps, or decide what to investigate next.",
|
|
3572
|
+
parameters: {
|
|
3573
|
+
type: "object",
|
|
3574
|
+
properties: {},
|
|
3575
|
+
additionalProperties: false
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3605
3578
|
}
|
|
3606
3579
|
];
|
|
3607
3580
|
var TOOL_META = {
|
|
@@ -3621,7 +3594,10 @@ var TOOL_META = {
|
|
|
3621
3594
|
ask_user: { parallelSafe: false },
|
|
3622
3595
|
create_paper: { parallelSafe: false },
|
|
3623
3596
|
create_tasks: { parallelSafe: true },
|
|
3624
|
-
update_task: { parallelSafe: true }
|
|
3597
|
+
update_task: { parallelSafe: true },
|
|
3598
|
+
web_search: { parallelSafe: true },
|
|
3599
|
+
query_ontology: { parallelSafe: true },
|
|
3600
|
+
ontology_status: { parallelSafe: true }
|
|
3625
3601
|
};
|
|
3626
3602
|
function isParallelSafe(toolName) {
|
|
3627
3603
|
return TOOL_META[toolName]?.parallelSafe ?? false;
|
|
@@ -3633,6 +3609,7 @@ var PLANNING_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
3633
3609
|
"search_workspace",
|
|
3634
3610
|
"fetch_url",
|
|
3635
3611
|
"search_external_sources",
|
|
3612
|
+
"web_search",
|
|
3636
3613
|
"load_skill",
|
|
3637
3614
|
"read_skill_reference",
|
|
3638
3615
|
"ask_user"
|
|
@@ -3691,9 +3668,9 @@ Root: ${process.cwd()}`,
|
|
|
3691
3668
|
}
|
|
3692
3669
|
|
|
3693
3670
|
// src/lib/agent/tools/read-file.ts
|
|
3694
|
-
import
|
|
3671
|
+
import fs7 from "fs/promises";
|
|
3695
3672
|
import { createReadStream } from "fs";
|
|
3696
|
-
import
|
|
3673
|
+
import path6 from "path";
|
|
3697
3674
|
import os2 from "os";
|
|
3698
3675
|
import readline2 from "readline";
|
|
3699
3676
|
var MAX_OUTPUT_BYTES = 50 * 1024;
|
|
@@ -3752,10 +3729,10 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
3752
3729
|
".eot"
|
|
3753
3730
|
]);
|
|
3754
3731
|
function isBinaryByExtension(filePath) {
|
|
3755
|
-
return BINARY_EXTENSIONS.has(
|
|
3732
|
+
return BINARY_EXTENSIONS.has(path6.extname(filePath).toLowerCase());
|
|
3756
3733
|
}
|
|
3757
3734
|
async function isBinaryByContent(filePath) {
|
|
3758
|
-
const handle = await
|
|
3735
|
+
const handle = await fs7.open(filePath, "r");
|
|
3759
3736
|
try {
|
|
3760
3737
|
const buf = Buffer.alloc(4096);
|
|
3761
3738
|
const { bytesRead } = await handle.read(buf, 0, 4096, 0);
|
|
@@ -3778,7 +3755,7 @@ function truncateLine(line) {
|
|
|
3778
3755
|
}
|
|
3779
3756
|
function expandHome(filePath) {
|
|
3780
3757
|
if (filePath === "~") return os2.homedir();
|
|
3781
|
-
if (filePath.startsWith("~/")) return
|
|
3758
|
+
if (filePath.startsWith("~/")) return path6.join(os2.homedir(), filePath.slice(2));
|
|
3782
3759
|
return filePath;
|
|
3783
3760
|
}
|
|
3784
3761
|
async function streamReadFile(resolved, offset, limit) {
|
|
@@ -3817,10 +3794,10 @@ async function executeReadFile(args, ctx) {
|
|
|
3817
3794
|
if (filePath.startsWith("path:") && filePath in ctx.workspaceFiles) {
|
|
3818
3795
|
return formatFromString(filePath, ctx.workspaceFiles[filePath], offset, limit);
|
|
3819
3796
|
}
|
|
3820
|
-
const resolved =
|
|
3797
|
+
const resolved = path6.isAbsolute(filePath) ? filePath : path6.resolve(filePath);
|
|
3821
3798
|
let stat;
|
|
3822
3799
|
try {
|
|
3823
|
-
stat = await
|
|
3800
|
+
stat = await fs7.stat(resolved);
|
|
3824
3801
|
} catch {
|
|
3825
3802
|
if (filePath in ctx.workspaceFiles) {
|
|
3826
3803
|
return formatFromString(filePath, ctx.workspaceFiles[filePath], offset, limit);
|
|
@@ -3828,7 +3805,7 @@ async function executeReadFile(args, ctx) {
|
|
|
3828
3805
|
return `Error: File not found: ${args.file_path}`;
|
|
3829
3806
|
}
|
|
3830
3807
|
if (stat.isDirectory()) {
|
|
3831
|
-
const entries = await
|
|
3808
|
+
const entries = await fs7.readdir(resolved, { withFileTypes: true });
|
|
3832
3809
|
const lines = entries.sort((a, b) => {
|
|
3833
3810
|
if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
|
|
3834
3811
|
return a.name.localeCompare(b.name);
|
|
@@ -3892,8 +3869,8 @@ ${outputLines.join("\n")}${footer}
|
|
|
3892
3869
|
}
|
|
3893
3870
|
|
|
3894
3871
|
// src/lib/agent/tools/list-directory.ts
|
|
3895
|
-
import
|
|
3896
|
-
import
|
|
3872
|
+
import fs8 from "fs/promises";
|
|
3873
|
+
import path7 from "path";
|
|
3897
3874
|
var MAX_ENTRIES = 200;
|
|
3898
3875
|
var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
3899
3876
|
"node_modules",
|
|
@@ -3910,7 +3887,7 @@ var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
|
3910
3887
|
".env"
|
|
3911
3888
|
]);
|
|
3912
3889
|
async function listEntries(dirPath, ignore) {
|
|
3913
|
-
const entries = await
|
|
3890
|
+
const entries = await fs8.readdir(dirPath, { withFileTypes: true });
|
|
3914
3891
|
const results = [];
|
|
3915
3892
|
for (const entry of entries) {
|
|
3916
3893
|
if (ignore.has(entry.name) || entry.name.startsWith(".") && DEFAULT_IGNORE.has(entry.name)) {
|
|
@@ -3919,7 +3896,7 @@ async function listEntries(dirPath, ignore) {
|
|
|
3919
3896
|
let size = 0;
|
|
3920
3897
|
if (!entry.isDirectory()) {
|
|
3921
3898
|
try {
|
|
3922
|
-
const stat = await
|
|
3899
|
+
const stat = await fs8.stat(path7.join(dirPath, entry.name));
|
|
3923
3900
|
size = stat.size;
|
|
3924
3901
|
} catch {
|
|
3925
3902
|
}
|
|
@@ -3957,7 +3934,7 @@ async function walkTree(rootDir, currentDir, depth, maxDepth, ignore, lines, ind
|
|
|
3957
3934
|
if (depth < maxDepth) {
|
|
3958
3935
|
await walkTree(
|
|
3959
3936
|
rootDir,
|
|
3960
|
-
|
|
3937
|
+
path7.join(currentDir, entry.name),
|
|
3961
3938
|
depth + 1,
|
|
3962
3939
|
maxDepth,
|
|
3963
3940
|
ignore,
|
|
@@ -3973,11 +3950,11 @@ async function walkTree(rootDir, currentDir, depth, maxDepth, ignore, lines, ind
|
|
|
3973
3950
|
}
|
|
3974
3951
|
}
|
|
3975
3952
|
async function executeListDirectory(args) {
|
|
3976
|
-
const dirPath = args.dir_path ?
|
|
3953
|
+
const dirPath = args.dir_path ? path7.isAbsolute(args.dir_path) ? args.dir_path : path7.resolve(args.dir_path) : process.cwd();
|
|
3977
3954
|
const maxDepth = Math.min(args.depth ?? 2, 5);
|
|
3978
3955
|
const ignore = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...args.ignore ?? []]);
|
|
3979
3956
|
try {
|
|
3980
|
-
const stat = await
|
|
3957
|
+
const stat = await fs8.stat(dirPath);
|
|
3981
3958
|
if (!stat.isDirectory()) {
|
|
3982
3959
|
return `Error: ${dirPath} is not a directory.`;
|
|
3983
3960
|
}
|
|
@@ -3992,8 +3969,8 @@ async function executeListDirectory(args) {
|
|
|
3992
3969
|
|
|
3993
3970
|
// src/lib/agent/tools/run-command.ts
|
|
3994
3971
|
import { spawn } from "child_process";
|
|
3995
|
-
import
|
|
3996
|
-
import
|
|
3972
|
+
import fs9 from "fs/promises";
|
|
3973
|
+
import path8 from "path";
|
|
3997
3974
|
var DEFAULT_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
3998
3975
|
var MAX_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3999
3976
|
var MAX_OUTPUT_BYTES2 = 50 * 1024;
|
|
@@ -4002,9 +3979,9 @@ async function executeRunCommand(args, signal) {
|
|
|
4002
3979
|
if (!command.trim()) {
|
|
4003
3980
|
return "Error: command is required.";
|
|
4004
3981
|
}
|
|
4005
|
-
const workdir = args.workdir ?
|
|
3982
|
+
const workdir = args.workdir ? path8.isAbsolute(args.workdir) ? args.workdir : path8.resolve(args.workdir) : process.cwd();
|
|
4006
3983
|
try {
|
|
4007
|
-
const stat = await
|
|
3984
|
+
const stat = await fs9.stat(workdir);
|
|
4008
3985
|
if (!stat.isDirectory()) {
|
|
4009
3986
|
return `Error: workdir is not a directory: ${workdir}`;
|
|
4010
3987
|
}
|
|
@@ -4128,142 +4105,6 @@ ${stderr}${stderrTruncated ? `
|
|
|
4128
4105
|
});
|
|
4129
4106
|
}
|
|
4130
4107
|
|
|
4131
|
-
// src/lib/agent/tools/fetch-url.ts
|
|
4132
|
-
import { load as loadCheerio } from "cheerio";
|
|
4133
|
-
var MAX_RESPONSE_BYTES = 512 * 1024;
|
|
4134
|
-
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
4135
|
-
var MAX_TIMEOUT_MS2 = 12e4;
|
|
4136
|
-
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";
|
|
4137
|
-
function htmlToText(html) {
|
|
4138
|
-
const $ = loadCheerio(html);
|
|
4139
|
-
$("script, style, noscript, nav, footer, header, aside, iframe, svg").remove();
|
|
4140
|
-
const title = $("title").first().text().trim();
|
|
4141
|
-
const mainEl = $("main").length > 0 ? $("main") : $("article").length > 0 ? $("article") : $("body");
|
|
4142
|
-
const sections = [];
|
|
4143
|
-
if (title) sections.push(`# ${title}
|
|
4144
|
-
`);
|
|
4145
|
-
mainEl.find("h1, h2, h3, h4, h5, h6, p, li, td, th, blockquote, pre, dd, dt, figcaption").each((_, el) => {
|
|
4146
|
-
const tag = el.tagName?.toLowerCase() ?? "";
|
|
4147
|
-
const text = $(el).text().replace(/\s+/g, " ").trim();
|
|
4148
|
-
if (!text) return;
|
|
4149
|
-
if (tag === "h1") sections.push(`
|
|
4150
|
-
# ${text}`);
|
|
4151
|
-
else if (tag === "h2") sections.push(`
|
|
4152
|
-
## ${text}`);
|
|
4153
|
-
else if (tag === "h3") sections.push(`
|
|
4154
|
-
### ${text}`);
|
|
4155
|
-
else if (tag.startsWith("h")) sections.push(`
|
|
4156
|
-
#### ${text}`);
|
|
4157
|
-
else if (tag === "li") sections.push(`- ${text}`);
|
|
4158
|
-
else if (tag === "blockquote") sections.push(`> ${text}`);
|
|
4159
|
-
else if (tag === "pre") sections.push(`\`\`\`
|
|
4160
|
-
${text}
|
|
4161
|
-
\`\`\``);
|
|
4162
|
-
else sections.push(text);
|
|
4163
|
-
});
|
|
4164
|
-
if (sections.length <= 1) {
|
|
4165
|
-
const bodyText = mainEl.text().replace(/\s+/g, " ").trim();
|
|
4166
|
-
if (title) return `# ${title}
|
|
4167
|
-
|
|
4168
|
-
${bodyText}`;
|
|
4169
|
-
return bodyText;
|
|
4170
|
-
}
|
|
4171
|
-
return sections.join("\n");
|
|
4172
|
-
}
|
|
4173
|
-
async function executeFetchUrl(args, signal) {
|
|
4174
|
-
const url = args.url.trim();
|
|
4175
|
-
if (!url) return "Error: url is required.";
|
|
4176
|
-
let parsed;
|
|
4177
|
-
try {
|
|
4178
|
-
parsed = new URL(url);
|
|
4179
|
-
} catch {
|
|
4180
|
-
return `Error: Invalid URL: ${url}`;
|
|
4181
|
-
}
|
|
4182
|
-
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
4183
|
-
return `Error: Only http and https URLs are supported.`;
|
|
4184
|
-
}
|
|
4185
|
-
const timeout = Math.min(
|
|
4186
|
-
Math.max(args.timeout ?? DEFAULT_TIMEOUT_MS2, 5e3),
|
|
4187
|
-
MAX_TIMEOUT_MS2
|
|
4188
|
-
);
|
|
4189
|
-
const format = args.format ?? "text";
|
|
4190
|
-
const timeoutController = new AbortController();
|
|
4191
|
-
const timer = setTimeout(() => timeoutController.abort(), timeout);
|
|
4192
|
-
const combinedSignal = signal ? AbortSignal.any([signal, timeoutController.signal]) : timeoutController.signal;
|
|
4193
|
-
try {
|
|
4194
|
-
let response = await fetch(url, {
|
|
4195
|
-
headers: { "User-Agent": USER_AGENT, Accept: "text/html,application/json,text/plain,*/*" },
|
|
4196
|
-
redirect: "follow",
|
|
4197
|
-
signal: combinedSignal
|
|
4198
|
-
});
|
|
4199
|
-
if (response.status === 403 && response.headers.get("cf-mitigated") === "challenge") {
|
|
4200
|
-
response = await fetch(url, {
|
|
4201
|
-
headers: { "User-Agent": "open-research-cli/0.1", Accept: "text/html,application/json,text/plain,*/*" },
|
|
4202
|
-
redirect: "follow",
|
|
4203
|
-
signal: combinedSignal
|
|
4204
|
-
});
|
|
4205
|
-
}
|
|
4206
|
-
if (!response.ok) {
|
|
4207
|
-
return `Error: HTTP ${response.status} ${response.statusText}`;
|
|
4208
|
-
}
|
|
4209
|
-
const finalUrl = response.url;
|
|
4210
|
-
const redirectNote = finalUrl && finalUrl !== url ? `(Redirected to: ${finalUrl})
|
|
4211
|
-
|
|
4212
|
-
` : "";
|
|
4213
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
4214
|
-
if (contentType.includes("image/") || contentType.includes("audio/") || contentType.includes("video/") || contentType.includes("application/octet-stream") || contentType.includes("application/zip")) {
|
|
4215
|
-
const length = response.headers.get("content-length");
|
|
4216
|
-
return `${redirectNote}Binary content: ${contentType}${length ? ` (${(Number(length) / 1024).toFixed(1)} KB)` : ""}. Use run_command with curl to download.`;
|
|
4217
|
-
}
|
|
4218
|
-
const reader = response.body?.getReader();
|
|
4219
|
-
if (!reader) return "Error: No response body.";
|
|
4220
|
-
const chunks = [];
|
|
4221
|
-
let totalBytes = 0;
|
|
4222
|
-
let truncated = false;
|
|
4223
|
-
while (true) {
|
|
4224
|
-
const { done, value } = await reader.read();
|
|
4225
|
-
if (done) break;
|
|
4226
|
-
if (totalBytes + value.length > MAX_RESPONSE_BYTES) {
|
|
4227
|
-
const remaining = MAX_RESPONSE_BYTES - totalBytes;
|
|
4228
|
-
if (remaining > 0) chunks.push(value.subarray(0, remaining));
|
|
4229
|
-
totalBytes = MAX_RESPONSE_BYTES;
|
|
4230
|
-
truncated = true;
|
|
4231
|
-
reader.cancel();
|
|
4232
|
-
break;
|
|
4233
|
-
}
|
|
4234
|
-
chunks.push(value);
|
|
4235
|
-
totalBytes += value.length;
|
|
4236
|
-
}
|
|
4237
|
-
const raw = new TextDecoder().decode(
|
|
4238
|
-
chunks.length === 1 ? chunks[0] : Buffer.concat(chunks)
|
|
4239
|
-
);
|
|
4240
|
-
const truncSuffix = truncated ? "\n\n(Response truncated to 512 KB)" : "";
|
|
4241
|
-
if (contentType.includes("application/json")) {
|
|
4242
|
-
try {
|
|
4243
|
-
const obj = JSON.parse(raw);
|
|
4244
|
-
return redirectNote + JSON.stringify(obj, null, 2) + truncSuffix;
|
|
4245
|
-
} catch {
|
|
4246
|
-
}
|
|
4247
|
-
}
|
|
4248
|
-
if (contentType.includes("text/html")) {
|
|
4249
|
-
if (format === "html") {
|
|
4250
|
-
return redirectNote + raw + truncSuffix;
|
|
4251
|
-
}
|
|
4252
|
-
const text = htmlToText(raw);
|
|
4253
|
-
return redirectNote + text + truncSuffix;
|
|
4254
|
-
}
|
|
4255
|
-
return redirectNote + raw + truncSuffix;
|
|
4256
|
-
} catch (err) {
|
|
4257
|
-
if (signal?.aborted) return "Fetch aborted by user.";
|
|
4258
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
4259
|
-
return `Fetch timed out after ${(timeout / 1e3).toFixed(0)}s.`;
|
|
4260
|
-
}
|
|
4261
|
-
return `Fetch error: ${err instanceof Error ? err.message : String(err)}`;
|
|
4262
|
-
} finally {
|
|
4263
|
-
clearTimeout(timer);
|
|
4264
|
-
}
|
|
4265
|
-
}
|
|
4266
|
-
|
|
4267
4108
|
// src/lib/agent/tools/ask-user.ts
|
|
4268
4109
|
var pendingQuestions = [];
|
|
4269
4110
|
function getPendingQuestion() {
|
|
@@ -4549,8 +4390,8 @@ function executeCreatePaper(args, ctx) {
|
|
|
4549
4390
|
}
|
|
4550
4391
|
|
|
4551
4392
|
// src/lib/agent/tools/read-pdf.ts
|
|
4552
|
-
import
|
|
4553
|
-
import
|
|
4393
|
+
import path9 from "path";
|
|
4394
|
+
import fs10 from "fs/promises";
|
|
4554
4395
|
var MAX_OUTPUT_BYTES3 = 5e4;
|
|
4555
4396
|
function parsePages(pages) {
|
|
4556
4397
|
const range = pages.match(/^(\d+)\s*-\s*(\d+)$/);
|
|
@@ -4560,8 +4401,8 @@ function parsePages(pages) {
|
|
|
4560
4401
|
return null;
|
|
4561
4402
|
}
|
|
4562
4403
|
async function executeReadPdf(args) {
|
|
4563
|
-
const resolved =
|
|
4564
|
-
const stat = await
|
|
4404
|
+
const resolved = path9.resolve(args.file_path);
|
|
4405
|
+
const stat = await fs10.stat(resolved).catch(() => null);
|
|
4565
4406
|
if (!stat || !stat.isFile()) {
|
|
4566
4407
|
return `Error: file not found: ${resolved}`;
|
|
4567
4408
|
}
|
|
@@ -5381,6 +5222,7 @@ function normalizeArxivPaper(paper) {
|
|
|
5381
5222
|
title: paper.title,
|
|
5382
5223
|
url: sourceUrl ?? pdfUrl ?? "",
|
|
5383
5224
|
pdfUrl,
|
|
5225
|
+
abstract: paper.summary?.trim() || void 0,
|
|
5384
5226
|
publishedDate: normalizeDatePrefix(paper.published),
|
|
5385
5227
|
author: paper.authors[0],
|
|
5386
5228
|
provider: "arxiv",
|
|
@@ -5662,11 +5504,14 @@ async function discoverScholarlySources({
|
|
|
5662
5504
|
}
|
|
5663
5505
|
|
|
5664
5506
|
// src/lib/agent/search-external-sources.ts
|
|
5665
|
-
async function executeSearchExternalSources(args, ctx) {
|
|
5507
|
+
async function executeSearchExternalSources(args, ctx, provider) {
|
|
5666
5508
|
const searches = args.searches ?? [];
|
|
5667
5509
|
if (searches.length === 0) {
|
|
5668
5510
|
return { result: "Error: no search queries provided.", sources: [] };
|
|
5669
5511
|
}
|
|
5512
|
+
if (!args.target) {
|
|
5513
|
+
return { result: "Error: target is required. Specify what information you need from the papers.", sources: [] };
|
|
5514
|
+
}
|
|
5670
5515
|
const primary = searches[0];
|
|
5671
5516
|
const variations = searches.slice(1).map((item) => item.query);
|
|
5672
5517
|
const config = await loadOpenResearchConfig().catch(() => null);
|
|
@@ -5678,16 +5523,74 @@ async function executeSearchExternalSources(args, ctx) {
|
|
|
5678
5523
|
semanticScholarApiKey: getSemanticScholarApiKey(config),
|
|
5679
5524
|
openAlexApiKey: getOpenAlexApiKey(config)
|
|
5680
5525
|
});
|
|
5681
|
-
|
|
5526
|
+
if (results.length === 0) {
|
|
5527
|
+
return { result: "No papers found.", sources: [] };
|
|
5528
|
+
}
|
|
5529
|
+
if (!provider) {
|
|
5530
|
+
const summary = results.map((r, i) => `${i + 1}. ${r.title} [${r.provider}] ${r.url}`).join("\n");
|
|
5531
|
+
return { result: summary || "No papers found.", sources: results };
|
|
5532
|
+
}
|
|
5533
|
+
const contentResults = await Promise.allSettled(
|
|
5534
|
+
results.map(async (source2) => {
|
|
5535
|
+
if (source2.abstract) {
|
|
5536
|
+
return { source: source2, text: source2.abstract };
|
|
5537
|
+
}
|
|
5538
|
+
if (source2.pdfUrl) {
|
|
5539
|
+
const content = await fetchAndParseContent(source2.pdfUrl);
|
|
5540
|
+
if (content) return { source: source2, text: content.text };
|
|
5541
|
+
}
|
|
5542
|
+
if (source2.url) {
|
|
5543
|
+
const content = await fetchAndParseContent(source2.url);
|
|
5544
|
+
if (content) return { source: source2, text: content.text };
|
|
5545
|
+
}
|
|
5546
|
+
return null;
|
|
5547
|
+
})
|
|
5548
|
+
);
|
|
5549
|
+
const extractionInputs = [];
|
|
5550
|
+
const sourceMap = /* @__PURE__ */ new Map();
|
|
5551
|
+
for (const result of contentResults) {
|
|
5552
|
+
if (result.status === "fulfilled" && result.value) {
|
|
5553
|
+
const { source: source2, text } = result.value;
|
|
5554
|
+
extractionInputs.push({
|
|
5555
|
+
title: source2.title,
|
|
5556
|
+
content: text,
|
|
5557
|
+
url: source2.url,
|
|
5558
|
+
target: args.target
|
|
5559
|
+
});
|
|
5560
|
+
sourceMap.set(source2.url, source2);
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
if (extractionInputs.length === 0) {
|
|
5564
|
+
const summary = results.map((r, i) => `${i + 1}. ${r.title} [${r.provider}] ${r.url}`).join("\n");
|
|
5565
|
+
return {
|
|
5566
|
+
result: `Could not fetch content from any papers. Metadata only:
|
|
5567
|
+
${summary}`,
|
|
5568
|
+
sources: results
|
|
5569
|
+
};
|
|
5570
|
+
}
|
|
5571
|
+
const extractions = await extractBatch(extractionInputs, provider);
|
|
5572
|
+
const extracted = [];
|
|
5573
|
+
for (const [url, extraction] of extractions) {
|
|
5574
|
+
const source2 = sourceMap.get(url);
|
|
5575
|
+
if (source2 && extraction.relevanceScore >= 2) {
|
|
5576
|
+
extracted.push({
|
|
5577
|
+
title: source2.title,
|
|
5578
|
+
url: source2.url,
|
|
5579
|
+
provider: source2.provider,
|
|
5580
|
+
extraction
|
|
5581
|
+
});
|
|
5582
|
+
}
|
|
5583
|
+
}
|
|
5584
|
+
const formatted = formatExtractionResults(extracted);
|
|
5682
5585
|
return {
|
|
5683
|
-
result:
|
|
5586
|
+
result: formatted,
|
|
5684
5587
|
sources: results
|
|
5685
5588
|
};
|
|
5686
5589
|
}
|
|
5687
5590
|
|
|
5688
5591
|
// src/lib/skills/runtime.ts
|
|
5689
|
-
import
|
|
5690
|
-
import
|
|
5592
|
+
import fs11 from "fs/promises";
|
|
5593
|
+
import path10 from "path";
|
|
5691
5594
|
import matter2 from "gray-matter";
|
|
5692
5595
|
async function loadRuntimeSkillByName(input2) {
|
|
5693
5596
|
const skills2 = await listAvailableSkills({ homeDir: input2.homeDir });
|
|
@@ -5695,7 +5598,7 @@ async function loadRuntimeSkillByName(input2) {
|
|
|
5695
5598
|
if (!match) {
|
|
5696
5599
|
return null;
|
|
5697
5600
|
}
|
|
5698
|
-
const raw = await
|
|
5601
|
+
const raw = await fs11.readFile(path10.join(match.skillDir, "SKILL.md"), "utf8");
|
|
5699
5602
|
const parsed = matter2(raw);
|
|
5700
5603
|
return {
|
|
5701
5604
|
id: match.name,
|
|
@@ -5706,8 +5609,8 @@ async function loadRuntimeSkillByName(input2) {
|
|
|
5706
5609
|
};
|
|
5707
5610
|
}
|
|
5708
5611
|
async function readSkillReferenceFile(skillDir, referencePath) {
|
|
5709
|
-
const fullPath =
|
|
5710
|
-
return
|
|
5612
|
+
const fullPath = path10.join(skillDir, "references", referencePath);
|
|
5613
|
+
return fs11.readFile(fullPath, "utf8");
|
|
5711
5614
|
}
|
|
5712
5615
|
|
|
5713
5616
|
// src/lib/agent/subagent/configs.ts
|
|
@@ -5912,8 +5815,8 @@ function describeSubAgentTool(name, args) {
|
|
|
5912
5815
|
}
|
|
5913
5816
|
|
|
5914
5817
|
// src/lib/agent/tools/tasks.ts
|
|
5915
|
-
import
|
|
5916
|
-
import
|
|
5818
|
+
import path11 from "path";
|
|
5819
|
+
import fs12 from "fs/promises";
|
|
5917
5820
|
var tasks = [];
|
|
5918
5821
|
var storePath = null;
|
|
5919
5822
|
function shortId() {
|
|
@@ -5923,12 +5826,12 @@ async function persist() {
|
|
|
5923
5826
|
if (!storePath) return;
|
|
5924
5827
|
const live = tasks.filter((t) => t.status !== "deleted");
|
|
5925
5828
|
const tmpPath = storePath + ".tmp";
|
|
5926
|
-
await
|
|
5927
|
-
await
|
|
5928
|
-
await
|
|
5829
|
+
await fs12.mkdir(path11.dirname(storePath), { recursive: true });
|
|
5830
|
+
await fs12.writeFile(tmpPath, JSON.stringify({ version: 1, tasks: live }, null, 2));
|
|
5831
|
+
await fs12.rename(tmpPath, storePath);
|
|
5929
5832
|
}
|
|
5930
5833
|
async function initTaskStore(workspaceDir) {
|
|
5931
|
-
storePath =
|
|
5834
|
+
storePath = path11.join(workspaceDir, ".open-research", "tasks.json");
|
|
5932
5835
|
const data = await readJsonFile(storePath, { version: 1, tasks: [] });
|
|
5933
5836
|
tasks = data.tasks ?? [];
|
|
5934
5837
|
}
|
|
@@ -6051,10 +5954,19 @@ async function executeTool(name, args, ctx, activeSkills, homeDir, signal, provi
|
|
|
6051
5954
|
case "search_external_sources": {
|
|
6052
5955
|
const out = await executeSearchExternalSources(
|
|
6053
5956
|
args,
|
|
6054
|
-
ctx
|
|
5957
|
+
ctx,
|
|
5958
|
+
provider
|
|
6055
5959
|
);
|
|
6056
5960
|
return { result: out.result, searchResults: out.sources };
|
|
6057
5961
|
}
|
|
5962
|
+
case "web_search": {
|
|
5963
|
+
const { executeWebSearch } = await import("./web-search-B7D5WMHU.js");
|
|
5964
|
+
const out = await executeWebSearch(
|
|
5965
|
+
args,
|
|
5966
|
+
provider
|
|
5967
|
+
);
|
|
5968
|
+
return { result: out.result };
|
|
5969
|
+
}
|
|
6058
5970
|
// ── User Interaction ────────────────────────────────────────────────
|
|
6059
5971
|
case "ask_user":
|
|
6060
5972
|
return {
|
|
@@ -6122,6 +6034,28 @@ ${meta}` };
|
|
|
6122
6034
|
return {
|
|
6123
6035
|
result: executeUpdateTask(args)
|
|
6124
6036
|
};
|
|
6037
|
+
// ── Ontology ──────────────────────────────────────────────────────────
|
|
6038
|
+
case "query_ontology": {
|
|
6039
|
+
if (!provider) {
|
|
6040
|
+
return { result: "Error: query_ontology requires an LLM provider." };
|
|
6041
|
+
}
|
|
6042
|
+
const { runQueryAgent } = await import("./query-agent-LRUUJR4F.js");
|
|
6043
|
+
const workspaceDir = ctx.workspaceDir ?? process.cwd();
|
|
6044
|
+
const answer = await runQueryAgent({
|
|
6045
|
+
query: String(args.query ?? ""),
|
|
6046
|
+
scope: args.scope ? String(args.scope) : void 0,
|
|
6047
|
+
provider,
|
|
6048
|
+
workspaceDir
|
|
6049
|
+
});
|
|
6050
|
+
return { result: answer };
|
|
6051
|
+
}
|
|
6052
|
+
case "ontology_status": {
|
|
6053
|
+
const { loadOntology } = await import("./store-LT5EGDOI.js");
|
|
6054
|
+
const { getOntologyStatus } = await import("./status-GEEAGLPF.js");
|
|
6055
|
+
const workspaceDir = ctx.workspaceDir ?? process.cwd();
|
|
6056
|
+
const ontology = await loadOntology(workspaceDir);
|
|
6057
|
+
return { result: getOntologyStatus(ontology) };
|
|
6058
|
+
}
|
|
6125
6059
|
default:
|
|
6126
6060
|
return { result: `Unknown tool: "${name}"` };
|
|
6127
6061
|
}
|
|
@@ -6381,17 +6315,17 @@ async function manualCompact(messages, model, provider, usage, customInstruction
|
|
|
6381
6315
|
}
|
|
6382
6316
|
|
|
6383
6317
|
// src/lib/memory/store.ts
|
|
6384
|
-
import
|
|
6385
|
-
import
|
|
6318
|
+
import fs13 from "fs/promises";
|
|
6319
|
+
import path12 from "path";
|
|
6386
6320
|
function getGlobalMemoryFile(options) {
|
|
6387
|
-
return
|
|
6321
|
+
return path12.join(getOpenResearchRoot(options), "memory.json");
|
|
6388
6322
|
}
|
|
6389
6323
|
function getProjectMemoryFile(workspaceDir) {
|
|
6390
|
-
return
|
|
6324
|
+
return path12.join(workspaceDir, ".open-research", "memory.json");
|
|
6391
6325
|
}
|
|
6392
6326
|
async function loadMemoryFile(filePath) {
|
|
6393
6327
|
try {
|
|
6394
|
-
const raw = await
|
|
6328
|
+
const raw = await fs13.readFile(filePath, "utf8");
|
|
6395
6329
|
const store = JSON.parse(raw);
|
|
6396
6330
|
return store.memories ?? [];
|
|
6397
6331
|
} catch {
|
|
@@ -6399,9 +6333,9 @@ async function loadMemoryFile(filePath) {
|
|
|
6399
6333
|
}
|
|
6400
6334
|
}
|
|
6401
6335
|
async function saveMemoryFile(filePath, memories) {
|
|
6402
|
-
await
|
|
6336
|
+
await fs13.mkdir(path12.dirname(filePath), { recursive: true });
|
|
6403
6337
|
const store = { version: 2, memories };
|
|
6404
|
-
await
|
|
6338
|
+
await fs13.writeFile(filePath, JSON.stringify(store, null, 2), "utf8");
|
|
6405
6339
|
}
|
|
6406
6340
|
async function loadGlobalMemories(options) {
|
|
6407
6341
|
const mems = await loadMemoryFile(getGlobalMemoryFile(options));
|
|
@@ -6437,7 +6371,7 @@ function evictIfNeeded(memories) {
|
|
|
6437
6371
|
memories.length = MAX_MEMORIES_PER_STORE;
|
|
6438
6372
|
}
|
|
6439
6373
|
async function addMemory(memory, options) {
|
|
6440
|
-
const scope = memory.scope ?? (memory.category === "
|
|
6374
|
+
const scope = memory.scope ?? (memory.category === "context" ? "project" : "global");
|
|
6441
6375
|
const filePath = scope === "project" && options?.workspaceDir ? getProjectMemoryFile(options.workspaceDir) : getGlobalMemoryFile(options);
|
|
6442
6376
|
const memories = await loadMemoryFile(filePath);
|
|
6443
6377
|
const existing = findDuplicate(memories, memory.content);
|
|
@@ -6630,12 +6564,17 @@ CRITICAL RULES:
|
|
|
6630
6564
|
- Maximum 3 actions per exchange
|
|
6631
6565
|
- If nothing meaningful to remember, return an empty array
|
|
6632
6566
|
|
|
6567
|
+
IMPORTANT \u2014 what NOT to store as memories:
|
|
6568
|
+
- Research findings, claims, hypotheses, evidence, or source details \u2014 these belong in the ontology, not memory
|
|
6569
|
+
- Specific paper results, data points, or analytical conclusions \u2014 ontology handles these
|
|
6570
|
+
- Connections between findings, contradictions, or evidence chains \u2014 ontology handles these
|
|
6571
|
+
Memory is for WHO the researcher is and HOW they work. The ontology handles WHAT they know.
|
|
6572
|
+
|
|
6633
6573
|
Categories:
|
|
6634
6574
|
- "user" \u2014 identity, role, field, institution (\u2192 stored globally)
|
|
6635
|
-
- "preference" \u2014 tools, style,
|
|
6636
|
-
- "
|
|
6637
|
-
- "
|
|
6638
|
-
- "context" \u2014 deadlines, collaborators, constraints (\u2192 stored per-project)
|
|
6575
|
+
- "preference" \u2014 tools, citation style, writing preferences (\u2192 stored globally)
|
|
6576
|
+
- "methodology" \u2014 statistical approaches, frameworks, analytical habits (\u2192 stored globally)
|
|
6577
|
+
- "context" \u2014 deadlines, collaborators, target venues, project constraints (\u2192 stored per-project)
|
|
6639
6578
|
|
|
6640
6579
|
Existing memories:
|
|
6641
6580
|
{EXISTING_MEMORIES}
|
|
@@ -6681,7 +6620,7 @@ async function extractMemories(input2) {
|
|
|
6681
6620
|
if (!Array.isArray(parsed)) return [];
|
|
6682
6621
|
const valid = [];
|
|
6683
6622
|
for (const item of parsed) {
|
|
6684
|
-
if (typeof item.content === "string" && item.content.length > 5 && ["user", "preference", "
|
|
6623
|
+
if (typeof item.content === "string" && item.content.length > 5 && ["user", "preference", "methodology", "context"].includes(item.category) && ["create", "update"].includes(item.action)) {
|
|
6685
6624
|
valid.push({
|
|
6686
6625
|
action: item.action,
|
|
6687
6626
|
content: item.content,
|
|
@@ -6716,7 +6655,7 @@ async function extractAndStoreMemories(input2) {
|
|
|
6716
6655
|
results.push(saved);
|
|
6717
6656
|
}
|
|
6718
6657
|
} else {
|
|
6719
|
-
const scope = action.category === "
|
|
6658
|
+
const scope = action.category === "context" ? "project" : "global";
|
|
6720
6659
|
const saved = await addMemory(
|
|
6721
6660
|
{ content: action.content, category: action.category, scope },
|
|
6722
6661
|
{ homeDir: input2.homeDir, workspaceDir: input2.workspaceDir }
|
|
@@ -6728,18 +6667,18 @@ async function extractAndStoreMemories(input2) {
|
|
|
6728
6667
|
}
|
|
6729
6668
|
|
|
6730
6669
|
// src/lib/workspace/agents-md.ts
|
|
6731
|
-
import
|
|
6732
|
-
import
|
|
6670
|
+
import fs14 from "fs/promises";
|
|
6671
|
+
import path13 from "path";
|
|
6733
6672
|
var AGENTS_MD_FILENAME = "AGENTS.md";
|
|
6734
6673
|
async function readAgentsMd(workspaceDir) {
|
|
6735
6674
|
try {
|
|
6736
|
-
return await
|
|
6675
|
+
return await fs14.readFile(path13.join(workspaceDir, AGENTS_MD_FILENAME), "utf8");
|
|
6737
6676
|
} catch {
|
|
6738
6677
|
return "";
|
|
6739
6678
|
}
|
|
6740
6679
|
}
|
|
6741
6680
|
async function writeAgentsMd(workspaceDir, content) {
|
|
6742
|
-
await
|
|
6681
|
+
await fs14.writeFile(path13.join(workspaceDir, AGENTS_MD_FILENAME), content, "utf8");
|
|
6743
6682
|
}
|
|
6744
6683
|
var UPDATE_SYSTEM_PROMPT = `You maintain an AGENTS.md file for a research workspace. This file gives future agent sessions instant context about the project \u2014 what it's about, what's been done, key files, and current direction.
|
|
6745
6684
|
|
|
@@ -6817,7 +6756,14 @@ var TOOL_DESCRIPTIONS = {
|
|
|
6817
6756
|
return `Fetching URL`;
|
|
6818
6757
|
}
|
|
6819
6758
|
},
|
|
6820
|
-
search_external_sources: () =>
|
|
6759
|
+
search_external_sources: (a) => {
|
|
6760
|
+
const target = a.target;
|
|
6761
|
+
return target ? `Searching papers: "${target.slice(0, 50)}"` : "Searching academic papers";
|
|
6762
|
+
},
|
|
6763
|
+
web_search: (a) => {
|
|
6764
|
+
const query = a.query;
|
|
6765
|
+
return `Web search: "${query?.slice(0, 50) ?? ""}"`;
|
|
6766
|
+
},
|
|
6821
6767
|
ask_user: (a) => `Asking: ${a.question?.slice(0, 50) ?? "question"}`,
|
|
6822
6768
|
load_skill: (a) => `Loading skill: ${a.skill_id ?? ""}`,
|
|
6823
6769
|
read_skill_reference: (a) => `Reading skill reference: ${a.path ?? ""}`,
|
|
@@ -6884,8 +6830,9 @@ ${skill.prompt}`).join("\n\n");
|
|
|
6884
6830
|
"- `read_file` / `list_directory` / `search_workspace` \u2014 explore and read",
|
|
6885
6831
|
"- `run_command` \u2014 execute python, R, node, LaTeX, curl, git, etc.",
|
|
6886
6832
|
"- `write_new_file` / `update_existing_file` \u2014 create and edit workspace files",
|
|
6887
|
-
"- `search_external_sources` \u2014 search
|
|
6888
|
-
"- `
|
|
6833
|
+
"- `search_external_sources` \u2014 search academic papers and extract evidence for/against a target (arXiv, Semantic Scholar, OpenAlex)",
|
|
6834
|
+
"- `web_search` \u2014 search the web and extract evidence for/against a target (docs, blogs, datasets, news)",
|
|
6835
|
+
"- `fetch_url` \u2014 fetch a specific URL when you already know the address",
|
|
6889
6836
|
"- `ask_user` \u2014 ask clarifying questions when genuinely needed",
|
|
6890
6837
|
"- `load_skill` \u2014 activate specialized research workflows",
|
|
6891
6838
|
"- `launch_subagent` \u2014 delegate exploration to a lightweight sub-agent that runs on its own context window",
|
|
@@ -6944,11 +6891,28 @@ async function runAgentTurn(input2) {
|
|
|
6944
6891
|
const memoryBlock = formatMemoriesForPrompt(relevantMemories);
|
|
6945
6892
|
const agentsMd = input2.workspace.workspaceDir ? await readAgentsMd(input2.workspace.workspaceDir).catch(() => "") : "";
|
|
6946
6893
|
const taskBlock = getTaskContextBlock();
|
|
6894
|
+
let ontologyBlock = null;
|
|
6895
|
+
if (input2.workspace.workspaceDir) {
|
|
6896
|
+
try {
|
|
6897
|
+
const { loadOntology } = await import("./store-LT5EGDOI.js");
|
|
6898
|
+
const { runRelevanceAgent } = await import("./relevance-agent-CCN7JGTM.js");
|
|
6899
|
+
const { buildScaffoldingContext } = await import("./scaffolding-MSAICMWV.js");
|
|
6900
|
+
const ontology = await loadOntology(input2.workspace.workspaceDir);
|
|
6901
|
+
const relevantIds = await runRelevanceAgent({
|
|
6902
|
+
userMessage: input2.message,
|
|
6903
|
+
ontology,
|
|
6904
|
+
provider: input2.provider
|
|
6905
|
+
});
|
|
6906
|
+
ontologyBlock = buildScaffoldingContext(ontology, relevantIds);
|
|
6907
|
+
} catch {
|
|
6908
|
+
}
|
|
6909
|
+
}
|
|
6947
6910
|
const fullSystemPrompt = [
|
|
6948
6911
|
systemPrompt,
|
|
6949
6912
|
memoryBlock || null,
|
|
6950
6913
|
agentsMd ? `## Project Context (from AGENTS.md)
|
|
6951
6914
|
${agentsMd}` : null,
|
|
6915
|
+
ontologyBlock || null,
|
|
6952
6916
|
taskBlock || null
|
|
6953
6917
|
].filter(Boolean).join("\n\n");
|
|
6954
6918
|
let messages = [
|
|
@@ -6958,6 +6922,7 @@ ${agentsMd}` : null,
|
|
|
6958
6922
|
];
|
|
6959
6923
|
const proposedUpdates = [];
|
|
6960
6924
|
const searchResults = [];
|
|
6925
|
+
const collectedToolOutputs = [];
|
|
6961
6926
|
const signal = input2.signal;
|
|
6962
6927
|
for (; ; ) {
|
|
6963
6928
|
if (signal?.aborted) break;
|
|
@@ -7030,6 +6995,22 @@ ${agentsMd}` : null,
|
|
|
7030
6995
|
}).catch(() => {
|
|
7031
6996
|
});
|
|
7032
6997
|
}
|
|
6998
|
+
if (input2.workspace.workspaceDir) {
|
|
6999
|
+
import("./manager-queue-F4VVZMTE.js").then(({ enqueueOntologyManager }) => {
|
|
7000
|
+
enqueueOntologyManager({
|
|
7001
|
+
userMessage: input2.message,
|
|
7002
|
+
agentResponse: fullText,
|
|
7003
|
+
toolOutputs: collectedToolOutputs,
|
|
7004
|
+
sessionId: "",
|
|
7005
|
+
// optional tracking
|
|
7006
|
+
turnIndex: 0,
|
|
7007
|
+
provider: input2.provider,
|
|
7008
|
+
workspaceDir: input2.workspace.workspaceDir,
|
|
7009
|
+
onOntologyUpdated: input2.onOntologyUpdated
|
|
7010
|
+
});
|
|
7011
|
+
}).catch(() => {
|
|
7012
|
+
});
|
|
7013
|
+
}
|
|
7033
7014
|
return {
|
|
7034
7015
|
text: fullText,
|
|
7035
7016
|
proposedUpdates,
|
|
@@ -7137,6 +7118,11 @@ ${agentsMd}` : null,
|
|
|
7137
7118
|
tool_call_id: toolCall.id,
|
|
7138
7119
|
content: result.result
|
|
7139
7120
|
});
|
|
7121
|
+
collectedToolOutputs.push({
|
|
7122
|
+
tool: toolCall.name,
|
|
7123
|
+
input: toolCall.arguments,
|
|
7124
|
+
output: result.result
|
|
7125
|
+
});
|
|
7140
7126
|
}
|
|
7141
7127
|
}
|
|
7142
7128
|
}
|
|
@@ -7169,8 +7155,8 @@ function classifyUpdateRisk(update) {
|
|
|
7169
7155
|
}
|
|
7170
7156
|
|
|
7171
7157
|
// src/lib/workspace/apply-update.ts
|
|
7172
|
-
import
|
|
7173
|
-
import
|
|
7158
|
+
import fs15 from "fs/promises";
|
|
7159
|
+
import path14 from "path";
|
|
7174
7160
|
function resolveRelativePath(update) {
|
|
7175
7161
|
if (update.key.startsWith("path:")) {
|
|
7176
7162
|
return update.key.slice(5);
|
|
@@ -7191,9 +7177,9 @@ function resolveRelativePath(update) {
|
|
|
7191
7177
|
}
|
|
7192
7178
|
async function applyProposedUpdate(workspaceDir, update) {
|
|
7193
7179
|
const relativePath = resolveRelativePath(update);
|
|
7194
|
-
const absolutePath =
|
|
7195
|
-
await
|
|
7196
|
-
await
|
|
7180
|
+
const absolutePath = path14.join(workspaceDir, relativePath);
|
|
7181
|
+
await fs15.mkdir(path14.dirname(absolutePath), { recursive: true });
|
|
7182
|
+
await fs15.writeFile(absolutePath, update.content, "utf8");
|
|
7197
7183
|
return absolutePath;
|
|
7198
7184
|
}
|
|
7199
7185
|
|
|
@@ -7411,15 +7397,15 @@ function SessionPicker({ sessions, onSelect, onCancel }) {
|
|
|
7411
7397
|
}
|
|
7412
7398
|
|
|
7413
7399
|
// src/lib/cli/update-check.ts
|
|
7414
|
-
import
|
|
7415
|
-
import
|
|
7400
|
+
import fs16 from "fs/promises";
|
|
7401
|
+
import path15 from "path";
|
|
7416
7402
|
import os3 from "os";
|
|
7417
7403
|
var PACKAGE_NAME = "open-research";
|
|
7418
7404
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
7419
|
-
var STATE_FILE =
|
|
7405
|
+
var STATE_FILE = path15.join(os3.homedir(), ".open-research", "update-check.json");
|
|
7420
7406
|
async function readState() {
|
|
7421
7407
|
try {
|
|
7422
|
-
const raw = await
|
|
7408
|
+
const raw = await fs16.readFile(STATE_FILE, "utf8");
|
|
7423
7409
|
const parsed = JSON.parse(raw);
|
|
7424
7410
|
return {
|
|
7425
7411
|
lastCheck: typeof parsed.lastCheck === "number" ? parsed.lastCheck : 0,
|
|
@@ -7431,8 +7417,8 @@ async function readState() {
|
|
|
7431
7417
|
}
|
|
7432
7418
|
}
|
|
7433
7419
|
async function writeState(state) {
|
|
7434
|
-
await
|
|
7435
|
-
await
|
|
7420
|
+
await fs16.mkdir(path15.dirname(STATE_FILE), { recursive: true });
|
|
7421
|
+
await fs16.writeFile(STATE_FILE, JSON.stringify(state), "utf8");
|
|
7436
7422
|
}
|
|
7437
7423
|
function getCurrentVersion() {
|
|
7438
7424
|
return getPackageVersion();
|
|
@@ -7506,6 +7492,7 @@ var SLASH_COMMANDS = [
|
|
|
7506
7492
|
{ name: "doctor", aliases: [], description: "Diagnose auth, connectivity, and tool availability", category: "system" },
|
|
7507
7493
|
{ name: "preview", aliases: [], description: "Live preview a LaTeX file in browser (e.g. /preview papers/draft.tex)", category: "workspace" },
|
|
7508
7494
|
{ name: "memory", aliases: ["/memories"], description: "View or clear stored memories about you", category: "system" },
|
|
7495
|
+
{ name: "ontology", aliases: ["/onto"], description: "View or manage the research ontology", category: "workspace" },
|
|
7509
7496
|
{ name: "exit", aliases: ["/quit", "/q"], description: "Exit Open Research", category: "system" }
|
|
7510
7497
|
];
|
|
7511
7498
|
function matchSlashCommand(input2) {
|
|
@@ -7524,7 +7511,8 @@ function matchSlashCommand(input2) {
|
|
|
7524
7511
|
var SUBCOMMAND_HINTS = {
|
|
7525
7512
|
"api-keys": [
|
|
7526
7513
|
{ name: "api-keys semantic-scholar <key>", description: "Set your Semantic Scholar API key" },
|
|
7527
|
-
{ name: "api-keys openalex <key>", description: "Set your OpenAlex API key" }
|
|
7514
|
+
{ name: "api-keys openalex <key>", description: "Set your OpenAlex API key" },
|
|
7515
|
+
{ name: "api-keys brave <key>", description: "Set Brave Search API key for better web search" }
|
|
7528
7516
|
],
|
|
7529
7517
|
"config": [
|
|
7530
7518
|
{ name: "config theme dark|light", description: "Set color theme" },
|
|
@@ -8048,60 +8036,64 @@ function HomeScreen({
|
|
|
8048
8036
|
hasWorkspace,
|
|
8049
8037
|
fileCount,
|
|
8050
8038
|
skillCount,
|
|
8039
|
+
version,
|
|
8040
|
+
model,
|
|
8041
|
+
contextWindow,
|
|
8042
|
+
workspacePath,
|
|
8051
8043
|
width
|
|
8052
8044
|
}) {
|
|
8053
8045
|
const theme = useTheme();
|
|
8054
8046
|
const contentWidth = resolveWidth(width);
|
|
8055
8047
|
const bodyWidth = indentedWidth(contentWidth);
|
|
8048
|
+
const ctxLabel = contextWindow >= 1e3 ? `${Math.round(contextWindow / 1e3)}k` : String(contextWindow);
|
|
8049
|
+
const shortPath = workspacePath ? workspacePath.replace(process.env.HOME ?? "", "~") : process.cwd().replace(process.env.HOME ?? "", "~");
|
|
8056
8050
|
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginBottom: 1, width: contentWidth, children: [
|
|
8057
|
-
/* @__PURE__ */ jsxs3(Box4, {
|
|
8051
|
+
/* @__PURE__ */ jsxs3(Box4, { width: contentWidth, children: [
|
|
8052
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.accent, children: "\u26A1 " }),
|
|
8058
8053
|
/* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.accent, children: "Open Research" }),
|
|
8059
|
-
/* @__PURE__ */
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
/* @__PURE__ */ jsxs3(Box4, { width: contentWidth, children: [
|
|
8063
|
-
/* @__PURE__ */ jsxs3(Text4, { color: theme.warning, children: [
|
|
8064
|
-
GUTTER.pending,
|
|
8065
|
-
" "
|
|
8066
|
-
] }),
|
|
8067
|
-
/* @__PURE__ */ jsx4(Text4, { color: theme.warning, children: "Connect or add OpenAI credentials to get started" })
|
|
8054
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.muted, children: [
|
|
8055
|
+
" v",
|
|
8056
|
+
version
|
|
8068
8057
|
] }),
|
|
8069
|
-
/* @__PURE__ */ jsx4(
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
/* @__PURE__ */ jsxs3(Text4, { color: theme.secondary, children: [
|
|
8074
|
-
GUTTER.success,
|
|
8075
|
-
" "
|
|
8076
|
-
] }),
|
|
8077
|
-
/* @__PURE__ */ jsx4(Text4, { color: theme.secondary, children: "Connected" })
|
|
8058
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, children: " | " }),
|
|
8059
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.text, children: [
|
|
8060
|
+
"\u25C6 ",
|
|
8061
|
+
model
|
|
8078
8062
|
] }),
|
|
8063
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, children: " | " }),
|
|
8064
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.muted, children: [
|
|
8065
|
+
ctxLabel,
|
|
8066
|
+
" context"
|
|
8067
|
+
] })
|
|
8068
|
+
] }),
|
|
8069
|
+
/* @__PURE__ */ jsxs3(Box4, { marginLeft: 2, width: bodyWidth, children: [
|
|
8070
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, children: shortPath }),
|
|
8071
|
+
hasWorkspace && /* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, children: [
|
|
8072
|
+
" \xB7 ",
|
|
8073
|
+
fileCount,
|
|
8074
|
+
" files \xB7 ",
|
|
8075
|
+
skillCount,
|
|
8076
|
+
" skills"
|
|
8077
|
+
] })
|
|
8078
|
+
] }),
|
|
8079
|
+
!hasAuth && /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginTop: 1, width: contentWidth, children: [
|
|
8079
8080
|
/* @__PURE__ */ jsxs3(Box4, { width: contentWidth, children: [
|
|
8080
8081
|
/* @__PURE__ */ jsxs3(Text4, { color: theme.warning, children: [
|
|
8081
8082
|
GUTTER.pending,
|
|
8082
8083
|
" "
|
|
8083
8084
|
] }),
|
|
8084
|
-
/* @__PURE__ */ jsx4(Text4, { color: theme.warning, children: "
|
|
8085
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.warning, children: "Connect OpenAI to get started" })
|
|
8085
8086
|
] }),
|
|
8086
|
-
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, width: bodyWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.muted, wrap: "wrap", children: "/
|
|
8087
|
+
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, width: bodyWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.muted, wrap: "wrap", children: "/config apikey sk-... \xB7 /auth \xB7 /auth-codex" }) })
|
|
8087
8088
|
] }),
|
|
8088
|
-
hasAuth && hasWorkspace && /* @__PURE__ */
|
|
8089
|
-
/* @__PURE__ */ jsxs3(
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
" "
|
|
8093
|
-
] }),
|
|
8094
|
-
/* @__PURE__ */ jsx4(Text4, { color: theme.secondary, children: "Ready" }),
|
|
8095
|
-
/* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, children: [
|
|
8096
|
-
" \u2014 ",
|
|
8097
|
-
fileCount,
|
|
8098
|
-
" files \xB7 ",
|
|
8099
|
-
skillCount,
|
|
8100
|
-
" skills"
|
|
8101
|
-
] })
|
|
8089
|
+
hasAuth && !hasWorkspace && /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginTop: 1, width: contentWidth, children: /* @__PURE__ */ jsxs3(Box4, { width: contentWidth, children: [
|
|
8090
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.warning, children: [
|
|
8091
|
+
GUTTER.pending,
|
|
8092
|
+
" "
|
|
8102
8093
|
] }),
|
|
8103
|
-
/* @__PURE__ */ jsx4(
|
|
8104
|
-
] })
|
|
8094
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.warning, children: "Run /init to create a workspace" })
|
|
8095
|
+
] }) }),
|
|
8096
|
+
hasAuth && hasWorkspace && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, width: contentWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, children: "Ask a question, @mention a file, or type /help" }) })
|
|
8105
8097
|
] });
|
|
8106
8098
|
}
|
|
8107
8099
|
function SuggestionDropdown({
|
|
@@ -8339,8 +8331,8 @@ function useTerminalWidth() {
|
|
|
8339
8331
|
import { startTransition } from "react";
|
|
8340
8332
|
|
|
8341
8333
|
// src/lib/workspace/init-agents-md.ts
|
|
8342
|
-
import
|
|
8343
|
-
import
|
|
8334
|
+
import fs17 from "fs/promises";
|
|
8335
|
+
import path16 from "path";
|
|
8344
8336
|
var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
|
|
8345
8337
|
"node_modules",
|
|
8346
8338
|
".git",
|
|
@@ -8379,18 +8371,18 @@ async function scanDirectoryShallow(dir, maxDepth = 2, depth = 0) {
|
|
|
8379
8371
|
const results = [];
|
|
8380
8372
|
if (depth > maxDepth) return results;
|
|
8381
8373
|
try {
|
|
8382
|
-
const entries = await
|
|
8374
|
+
const entries = await fs17.readdir(dir, { withFileTypes: true });
|
|
8383
8375
|
for (const entry of entries) {
|
|
8384
8376
|
if (IGNORED_DIRS2.has(entry.name)) continue;
|
|
8385
8377
|
if (entry.name.startsWith(".") && depth === 0 && entry.isDirectory()) continue;
|
|
8386
|
-
const fullPath =
|
|
8387
|
-
const relativePath =
|
|
8378
|
+
const fullPath = path16.join(dir, entry.name);
|
|
8379
|
+
const relativePath = path16.relative(dir, fullPath);
|
|
8388
8380
|
if (entry.isDirectory()) {
|
|
8389
8381
|
results.push({ path: relativePath + "/", size: 0, isDir: true });
|
|
8390
8382
|
const children = await scanDirectoryShallow(fullPath, maxDepth, depth + 1);
|
|
8391
8383
|
results.push(...children);
|
|
8392
8384
|
} else {
|
|
8393
|
-
const stat = await
|
|
8385
|
+
const stat = await fs17.stat(fullPath).catch(() => null);
|
|
8394
8386
|
results.push({ path: relativePath, size: stat?.size ?? 0, isDir: false });
|
|
8395
8387
|
}
|
|
8396
8388
|
}
|
|
@@ -8402,7 +8394,7 @@ async function readKeyFiles(dir) {
|
|
|
8402
8394
|
const contents = {};
|
|
8403
8395
|
for (const name of INTERESTING_FILES) {
|
|
8404
8396
|
try {
|
|
8405
|
-
const content = await
|
|
8397
|
+
const content = await fs17.readFile(path16.join(dir, name), "utf8");
|
|
8406
8398
|
contents[name] = content.slice(0, 2e3);
|
|
8407
8399
|
} catch {
|
|
8408
8400
|
}
|
|
@@ -8488,8 +8480,8 @@ ${scanData.slice(0, 25e3)}`;
|
|
|
8488
8480
|
|
|
8489
8481
|
// src/lib/preview/server.ts
|
|
8490
8482
|
import http2 from "http";
|
|
8491
|
-
import
|
|
8492
|
-
import
|
|
8483
|
+
import fs18 from "fs";
|
|
8484
|
+
import path17 from "path";
|
|
8493
8485
|
|
|
8494
8486
|
// src/lib/preview/latex-to-html.ts
|
|
8495
8487
|
function latexToHtml(latex) {
|
|
@@ -8756,11 +8748,11 @@ var HTML_TEMPLATE = `<!DOCTYPE html>
|
|
|
8756
8748
|
</body>
|
|
8757
8749
|
</html>`;
|
|
8758
8750
|
function startPreviewServer(texPath) {
|
|
8759
|
-
const resolved =
|
|
8751
|
+
const resolved = path17.resolve(texPath);
|
|
8760
8752
|
let currentHash = "";
|
|
8761
8753
|
function getContentHash() {
|
|
8762
8754
|
try {
|
|
8763
|
-
const content =
|
|
8755
|
+
const content = fs18.readFileSync(resolved, "utf8");
|
|
8764
8756
|
return `${content.length}-${content.slice(0, 100)}-${content.slice(-100)}`;
|
|
8765
8757
|
} catch {
|
|
8766
8758
|
return "error";
|
|
@@ -8768,7 +8760,7 @@ function startPreviewServer(texPath) {
|
|
|
8768
8760
|
}
|
|
8769
8761
|
function renderPage() {
|
|
8770
8762
|
try {
|
|
8771
|
-
const latex =
|
|
8763
|
+
const latex = fs18.readFileSync(resolved, "utf8");
|
|
8772
8764
|
const htmlContent = latexToHtml(latex);
|
|
8773
8765
|
currentHash = getContentHash();
|
|
8774
8766
|
return HTML_TEMPLATE.replace("{{CONTENT}}", htmlContent);
|
|
@@ -8942,7 +8934,7 @@ async function executeSlashCommand(cmd, args, ctx) {
|
|
|
8942
8934
|
addSystemMessage("No workspace. Run /init first.");
|
|
8943
8935
|
break;
|
|
8944
8936
|
}
|
|
8945
|
-
const { listSessions: listSessions2 } = await import("./sessions-
|
|
8937
|
+
const { listSessions: listSessions2 } = await import("./sessions-GRES2MUV.js");
|
|
8946
8938
|
const foundSessions = await listSessions2(workspacePath);
|
|
8947
8939
|
if (foundSessions.length === 0) {
|
|
8948
8940
|
addSystemMessage("No previous sessions found.");
|
|
@@ -9164,8 +9156,8 @@ async function executeSlashCommand(cmd, args, ctx) {
|
|
|
9164
9156
|
}
|
|
9165
9157
|
case "export": {
|
|
9166
9158
|
const fileName = args?.trim() || "conversation-export.md";
|
|
9167
|
-
const
|
|
9168
|
-
const exportPath =
|
|
9159
|
+
const path20 = __require("path");
|
|
9160
|
+
const exportPath = path20.resolve(workspacePath ?? process.cwd(), fileName);
|
|
9169
9161
|
const lines = [`# Open Research \u2014 Conversation Export
|
|
9170
9162
|
`];
|
|
9171
9163
|
for (const msg of messages) {
|
|
@@ -9215,23 +9207,27 @@ ${msg.text}
|
|
|
9215
9207
|
if (!args) {
|
|
9216
9208
|
const ssKey = getSemanticScholarApiKey(config);
|
|
9217
9209
|
const oaKey = getOpenAlexApiKey(config);
|
|
9210
|
+
const braveKey = getBraveApiKey(config);
|
|
9218
9211
|
addSystemMessage("API Keys:");
|
|
9219
9212
|
addSystemMessage(` Semantic Scholar: ${ssKey ? ssKey.slice(0, 8) + "..." : "not set"}`);
|
|
9220
9213
|
addSystemMessage(` OpenAlex: ${oaKey ? oaKey.slice(0, 8) + "..." : "not set"}`);
|
|
9214
|
+
addSystemMessage(` Brave Search: ${braveKey ? braveKey.slice(0, 8) + "..." : "not set (using DuckDuckGo)"}`);
|
|
9221
9215
|
addSystemMessage("");
|
|
9222
9216
|
addSystemMessage("Set via CLI:");
|
|
9223
9217
|
addSystemMessage(" /api-keys semantic-scholar YOUR_KEY");
|
|
9224
9218
|
addSystemMessage(" /api-keys openalex YOUR_KEY");
|
|
9219
|
+
addSystemMessage(" /api-keys brave YOUR_KEY");
|
|
9225
9220
|
addSystemMessage("");
|
|
9226
9221
|
addSystemMessage("Or set environment variables:");
|
|
9227
9222
|
addSystemMessage(" export SEMANTIC_SCHOLAR_API_KEY=your_key");
|
|
9228
9223
|
addSystemMessage(" export OPENALEX_API_KEY=your_key");
|
|
9224
|
+
addSystemMessage(" export BRAVE_API_KEY=your_key");
|
|
9229
9225
|
break;
|
|
9230
9226
|
}
|
|
9231
9227
|
const [keyName, ...keyParts] = args.split(/\s+/);
|
|
9232
9228
|
const keyValue = keyParts.join("").trim();
|
|
9233
9229
|
if (!keyValue) {
|
|
9234
|
-
addSystemMessage("Usage: /api-keys <semantic-scholar|openalex> <key>");
|
|
9230
|
+
addSystemMessage("Usage: /api-keys <semantic-scholar|openalex|brave> <key>");
|
|
9235
9231
|
break;
|
|
9236
9232
|
}
|
|
9237
9233
|
if (config) {
|
|
@@ -9240,8 +9236,10 @@ ${msg.text}
|
|
|
9240
9236
|
apiKeys.semanticScholar = keyValue;
|
|
9241
9237
|
} else if (keyName === "openalex" || keyName === "oa") {
|
|
9242
9238
|
apiKeys.openAlex = keyValue;
|
|
9239
|
+
} else if (keyName === "brave") {
|
|
9240
|
+
apiKeys.brave = keyValue;
|
|
9243
9241
|
} else {
|
|
9244
|
-
addSystemMessage(`Unknown key: ${keyName}. Use semantic-scholar or
|
|
9242
|
+
addSystemMessage(`Unknown key: ${keyName}. Use semantic-scholar, openalex, or brave.`);
|
|
9245
9243
|
break;
|
|
9246
9244
|
}
|
|
9247
9245
|
const updated = { ...config, apiKeys };
|
|
@@ -9260,8 +9258,10 @@ ${msg.text}
|
|
|
9260
9258
|
addSystemMessage(` Skills: ${skills2.length} loaded`);
|
|
9261
9259
|
const ssKey = getSemanticScholarApiKey(config);
|
|
9262
9260
|
const oaKey = getOpenAlexApiKey(config);
|
|
9261
|
+
const brKey = getBraveApiKey(config);
|
|
9263
9262
|
addSystemMessage(` Semantic Scholar API: ${ssKey ? "configured" : "not set (rate-limited)"}`);
|
|
9264
9263
|
addSystemMessage(` OpenAlex API: ${oaKey ? "configured" : "not set (limited)"}`);
|
|
9264
|
+
addSystemMessage(` Brave Search API: ${brKey ? "configured" : "not set (using DuckDuckGo)"}`);
|
|
9265
9265
|
const mems = await loadAllMemories({ homeDir });
|
|
9266
9266
|
addSystemMessage(` Memories: ${mems.length} stored`);
|
|
9267
9267
|
addSystemMessage(` Node: ${process.version}`);
|
|
@@ -9329,6 +9329,76 @@ ${msg.text}
|
|
|
9329
9329
|
}
|
|
9330
9330
|
break;
|
|
9331
9331
|
}
|
|
9332
|
+
case "ontology": {
|
|
9333
|
+
const workspaceDir = workspacePath;
|
|
9334
|
+
if (!workspaceDir) {
|
|
9335
|
+
addSystemMessage("No workspace. Run /init first.");
|
|
9336
|
+
break;
|
|
9337
|
+
}
|
|
9338
|
+
const { loadOntology, saveOntology } = await import("./store-LT5EGDOI.js");
|
|
9339
|
+
const ontology = await loadOntology(workspaceDir);
|
|
9340
|
+
if (!args) {
|
|
9341
|
+
const { getOntologyStatus } = await import("./status-GEEAGLPF.js");
|
|
9342
|
+
addSystemMessage(getOntologyStatus(ontology));
|
|
9343
|
+
} else if (args === "claims") {
|
|
9344
|
+
const { formatClaims } = await import("./status-GEEAGLPF.js");
|
|
9345
|
+
addSystemMessage(formatClaims(ontology));
|
|
9346
|
+
} else if (args === "conflicts") {
|
|
9347
|
+
const { formatConflicts } = await import("./status-GEEAGLPF.js");
|
|
9348
|
+
addSystemMessage(formatConflicts(ontology));
|
|
9349
|
+
} else if (args.startsWith("around ")) {
|
|
9350
|
+
const term = args.slice(7).trim();
|
|
9351
|
+
if (!term) {
|
|
9352
|
+
addSystemMessage("Usage: /ontology around <term>");
|
|
9353
|
+
break;
|
|
9354
|
+
}
|
|
9355
|
+
const { searchNotes, getConnections } = await import("./read-tools-GHBKBZFE.js");
|
|
9356
|
+
const results = searchNotes(ontology, { queries: [term], limit: 5 });
|
|
9357
|
+
if (results.length === 0) {
|
|
9358
|
+
addSystemMessage(`No notes found matching "${term}".`);
|
|
9359
|
+
} else {
|
|
9360
|
+
for (const note of results) {
|
|
9361
|
+
addSystemMessage(`[${note.id.slice(0, 8)}] "${note.content}" (${note.kind}, ${note.confidence})`);
|
|
9362
|
+
const { connected } = getConnections(ontology, note.id, 1);
|
|
9363
|
+
for (const conn of connected) {
|
|
9364
|
+
const edge = note.edges.find((e) => e.targetId === conn.id);
|
|
9365
|
+
const rel = edge ? `${edge.relation} \u2192` : "\u2190 connected";
|
|
9366
|
+
addSystemMessage(` ${rel} [${conn.id.slice(0, 8)}] "${conn.content}" (${conn.kind})`);
|
|
9367
|
+
}
|
|
9368
|
+
}
|
|
9369
|
+
}
|
|
9370
|
+
} else if (args.startsWith("delete ")) {
|
|
9371
|
+
const noteId = args.slice(7).trim();
|
|
9372
|
+
const note = ontology.notes.find((n) => n.id.startsWith(noteId));
|
|
9373
|
+
if (!note) {
|
|
9374
|
+
addSystemMessage(`Note not found: ${noteId}`);
|
|
9375
|
+
} else {
|
|
9376
|
+
ontology.notes = ontology.notes.filter((n) => n.id !== note.id);
|
|
9377
|
+
for (const n of ontology.notes) {
|
|
9378
|
+
n.edges = n.edges.filter((e) => e.targetId !== note.id);
|
|
9379
|
+
}
|
|
9380
|
+
await saveOntology(ontology, workspaceDir);
|
|
9381
|
+
addSystemMessage(`Deleted note [${note.id.slice(0, 8)}] "${note.content}" and its edges.`);
|
|
9382
|
+
}
|
|
9383
|
+
} else if (args.startsWith("edit ")) {
|
|
9384
|
+
const noteId = args.slice(5).trim();
|
|
9385
|
+
const note = ontology.notes.find((n) => n.id.startsWith(noteId));
|
|
9386
|
+
if (!note) {
|
|
9387
|
+
addSystemMessage(`Note not found: ${noteId}`);
|
|
9388
|
+
} else {
|
|
9389
|
+
addSystemMessage(`Note [${note.id.slice(0, 8)}]:`);
|
|
9390
|
+
addSystemMessage(` Kind: ${note.kind}`);
|
|
9391
|
+
addSystemMessage(` Content: "${note.content}"`);
|
|
9392
|
+
addSystemMessage(` Confidence: ${note.confidence}`);
|
|
9393
|
+
addSystemMessage(` Edges: ${note.edges.length}`);
|
|
9394
|
+
addSystemMessage("");
|
|
9395
|
+
addSystemMessage('To edit, use the agent: "Update the ontology note about ..."');
|
|
9396
|
+
}
|
|
9397
|
+
} else {
|
|
9398
|
+
addSystemMessage("Usage: /ontology [claims|conflicts|around <term>|delete <id>|edit <id>]");
|
|
9399
|
+
}
|
|
9400
|
+
break;
|
|
9401
|
+
}
|
|
9332
9402
|
case "exit": {
|
|
9333
9403
|
exitApp();
|
|
9334
9404
|
break;
|
|
@@ -9353,7 +9423,8 @@ var TOOL_GROUPS = {
|
|
|
9353
9423
|
create_paper: "created paper",
|
|
9354
9424
|
read_skill_reference: "read",
|
|
9355
9425
|
create_tasks: "tasks",
|
|
9356
|
-
update_task: "tasks"
|
|
9426
|
+
update_task: "tasks",
|
|
9427
|
+
web_search: "web searched"
|
|
9357
9428
|
};
|
|
9358
9429
|
function buildToolSummary(tools) {
|
|
9359
9430
|
const groups = {};
|
|
@@ -9377,8 +9448,9 @@ function buildToolSummary(tools) {
|
|
|
9377
9448
|
if (groups["created paper"]) parts.push("Created paper");
|
|
9378
9449
|
if (groups["sub-agent"]) parts.push(`Ran ${groups["sub-agent"]} sub-agent${groups["sub-agent"] > 1 ? "s" : ""}`);
|
|
9379
9450
|
if (groups["tasks"]) parts.push(`Updated tasks`);
|
|
9451
|
+
if (groups["web searched"]) parts.push(`Web searched${groups["web searched"] > 1 ? ` (${groups["web searched"]}\xD7)` : ""}`);
|
|
9380
9452
|
for (const [group, count] of Object.entries(groups)) {
|
|
9381
|
-
if (!["read", "listed", "searched", "searched papers", "ran", "wrote", "edited", "fetched", "asked user", "loaded skill", "created paper", "sub-agent", "tasks"].includes(group)) {
|
|
9453
|
+
if (!["read", "listed", "searched", "searched papers", "ran", "wrote", "edited", "fetched", "asked user", "loaded skill", "created paper", "sub-agent", "tasks", "web searched"].includes(group)) {
|
|
9382
9454
|
parts.push(`${group} (${count})`);
|
|
9383
9455
|
}
|
|
9384
9456
|
}
|
|
@@ -10237,6 +10309,10 @@ ${error.stack}` : String(error)}` }
|
|
|
10237
10309
|
hasWorkspace,
|
|
10238
10310
|
fileCount: workspaceFiles.length,
|
|
10239
10311
|
skillCount: skills2.length,
|
|
10312
|
+
version: getPackageVersion(),
|
|
10313
|
+
model: config?.defaults.model ?? "gpt-5.4",
|
|
10314
|
+
contextWindow: getContextWindow(config?.defaults.model ?? "gpt-5.4"),
|
|
10315
|
+
workspacePath,
|
|
10240
10316
|
width: contentWidth
|
|
10241
10317
|
}
|
|
10242
10318
|
),
|
|
@@ -10350,7 +10426,7 @@ ${error.stack}` : String(error)}` }
|
|
|
10350
10426
|
statusParts,
|
|
10351
10427
|
statusColor,
|
|
10352
10428
|
tokenDisplay,
|
|
10353
|
-
workspaceName: hasWorkspace ?
|
|
10429
|
+
workspaceName: hasWorkspace ? path18.basename(workspacePath) : process.cwd(),
|
|
10354
10430
|
mode: agentMode,
|
|
10355
10431
|
planningStatus: planningState.status
|
|
10356
10432
|
}
|
|
@@ -10362,7 +10438,7 @@ ${error.stack}` : String(error)}` }
|
|
|
10362
10438
|
var program = new Command();
|
|
10363
10439
|
program.name("open-research").version(getPackageVersion()).description("Local-first research CLI powered by OpenAI account auth or API keys.").argument("[workspacePath]", "Optional workspace path to open").action(async (workspacePath) => {
|
|
10364
10440
|
await ensureOpenResearchConfig();
|
|
10365
|
-
const target = workspacePath ?
|
|
10441
|
+
const target = workspacePath ? path19.resolve(workspacePath) : process.cwd();
|
|
10366
10442
|
const project = await loadWorkspaceProject(target);
|
|
10367
10443
|
const hasProvider = await hasConfiguredProvider();
|
|
10368
10444
|
render(
|
|
@@ -10379,7 +10455,7 @@ program.name("open-research").version(getPackageVersion()).description("Local-fi
|
|
|
10379
10455
|
});
|
|
10380
10456
|
program.command("init").argument("[workspacePath]").description("Initialize an Open Research workspace.").action(async (workspacePath) => {
|
|
10381
10457
|
await ensureOpenResearchConfig();
|
|
10382
|
-
const target =
|
|
10458
|
+
const target = path19.resolve(workspacePath ?? process.cwd());
|
|
10383
10459
|
const project = await initWorkspace({ workspaceDir: target });
|
|
10384
10460
|
console.log(`Initialized workspace: ${target}`);
|
|
10385
10461
|
console.log(`Title: ${project.title}`);
|
|
@@ -10451,8 +10527,8 @@ skills.command("create").argument("[name]").description("Scaffold a new user ski
|
|
|
10451
10527
|
});
|
|
10452
10528
|
skills.command("edit").argument("<name>").description("Open a user skill in $EDITOR.").action(async (name) => {
|
|
10453
10529
|
await ensureOpenResearchConfig();
|
|
10454
|
-
const skillDir =
|
|
10455
|
-
openInEditor(
|
|
10530
|
+
const skillDir = path19.join(getOpenResearchSkillsDir(), name);
|
|
10531
|
+
openInEditor(path19.join(skillDir, "SKILL.md"));
|
|
10456
10532
|
const validation = await validateSkillDirectory({ skillDir });
|
|
10457
10533
|
if (!validation.ok) {
|
|
10458
10534
|
console.error(validation.errors.join("\n"));
|
|
@@ -10463,9 +10539,9 @@ skills.command("edit").argument("<name>").description("Open a user skill in $EDI
|
|
|
10463
10539
|
});
|
|
10464
10540
|
skills.command("validate").argument("[nameOrPath]").description("Validate one user skill.").action(async (nameOrPath) => {
|
|
10465
10541
|
await ensureOpenResearchConfig();
|
|
10466
|
-
const skillDir = nameOrPath ?
|
|
10542
|
+
const skillDir = nameOrPath ? path19.isAbsolute(nameOrPath) ? nameOrPath : path19.join(getOpenResearchSkillsDir(), nameOrPath) : getOpenResearchSkillsDir();
|
|
10467
10543
|
const stat = await import("fs/promises").then(
|
|
10468
|
-
(
|
|
10544
|
+
(fs19) => fs19.stat(skillDir).catch(() => null)
|
|
10469
10545
|
);
|
|
10470
10546
|
if (!stat) {
|
|
10471
10547
|
throw new Error(`Skill path not found: ${skillDir}`);
|