open-research 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +293 -48
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
8
8
|
|
|
9
9
|
// src/cli.ts
|
|
10
10
|
import React4 from "react";
|
|
11
|
-
import
|
|
11
|
+
import path21 from "path";
|
|
12
12
|
import { Command } from "commander";
|
|
13
13
|
import { render } from "ink";
|
|
14
14
|
|
|
@@ -811,7 +811,7 @@ function formatDateTime(value) {
|
|
|
811
811
|
}
|
|
812
812
|
|
|
813
813
|
// src/lib/cli/version.ts
|
|
814
|
-
var PACKAGE_VERSION = "0.1.
|
|
814
|
+
var PACKAGE_VERSION = "0.1.9";
|
|
815
815
|
function getPackageVersion() {
|
|
816
816
|
return PACKAGE_VERSION;
|
|
817
817
|
}
|
|
@@ -860,7 +860,7 @@ async function ensureOpenResearchConfig(options) {
|
|
|
860
860
|
}
|
|
861
861
|
|
|
862
862
|
// src/tui/app.tsx
|
|
863
|
-
import
|
|
863
|
+
import path20 from "path";
|
|
864
864
|
import {
|
|
865
865
|
startTransition,
|
|
866
866
|
useDeferredValue,
|
|
@@ -1526,10 +1526,28 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
1526
1526
|
".yml",
|
|
1527
1527
|
".csv"
|
|
1528
1528
|
]);
|
|
1529
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
1530
|
+
".open-research",
|
|
1531
|
+
"node_modules",
|
|
1532
|
+
".git",
|
|
1533
|
+
"dist",
|
|
1534
|
+
"build",
|
|
1535
|
+
".next",
|
|
1536
|
+
"__pycache__",
|
|
1537
|
+
".venv",
|
|
1538
|
+
"venv",
|
|
1539
|
+
".cache",
|
|
1540
|
+
"target",
|
|
1541
|
+
".tox",
|
|
1542
|
+
"coverage",
|
|
1543
|
+
".nyc_output",
|
|
1544
|
+
".parcel-cache",
|
|
1545
|
+
".turbo"
|
|
1546
|
+
]);
|
|
1529
1547
|
async function walkDir(rootDir, currentDir, out) {
|
|
1530
1548
|
const entries = await fs8.readdir(currentDir, { withFileTypes: true });
|
|
1531
1549
|
for (const entry of entries) {
|
|
1532
|
-
if (entry.name
|
|
1550
|
+
if (IGNORED_DIRS.has(entry.name)) {
|
|
1533
1551
|
continue;
|
|
1534
1552
|
}
|
|
1535
1553
|
const fullPath = path7.join(currentDir, entry.name);
|
|
@@ -2286,9 +2304,6 @@ function getToolsForMode(mode) {
|
|
|
2286
2304
|
|
|
2287
2305
|
// src/lib/agent/prompts/planning.ts
|
|
2288
2306
|
function buildPlanningSystemPrompt(ctx, activeSkills) {
|
|
2289
|
-
const workspaceMap = ctx.availableKeys.map(
|
|
2290
|
-
(key) => `- ${key}${ctx.fileLabels?.[key] ? ` \u2014 ${ctx.fileLabels[key]}` : ""}`
|
|
2291
|
-
).join("\n");
|
|
2292
2307
|
const skillText = activeSkills.map((skill) => `## Active Skill: ${skill.name}
|
|
2293
2308
|
${skill.prompt}`).join("\n\n");
|
|
2294
2309
|
return [
|
|
@@ -2354,8 +2369,9 @@ ${skill.prompt}`).join("\n\n");
|
|
|
2354
2369
|
"- If the user's intent is already crystal clear, you can produce the charter after just 1 question or even immediately.",
|
|
2355
2370
|
"- Ground your proposed steps in what you've learned from the workspace and external search results.",
|
|
2356
2371
|
"",
|
|
2357
|
-
|
|
2358
|
-
${
|
|
2372
|
+
`## Workspace
|
|
2373
|
+
Root: ${process.cwd()}
|
|
2374
|
+
Use list_directory to explore. Use search_workspace or read_file to read content.`,
|
|
2359
2375
|
skillText
|
|
2360
2376
|
].filter(Boolean).join("\n");
|
|
2361
2377
|
}
|
|
@@ -4913,6 +4929,72 @@ async function extractAndStoreMemories(input2) {
|
|
|
4913
4929
|
return stored;
|
|
4914
4930
|
}
|
|
4915
4931
|
|
|
4932
|
+
// src/lib/workspace/agents-md.ts
|
|
4933
|
+
import fs15 from "fs/promises";
|
|
4934
|
+
import path14 from "path";
|
|
4935
|
+
var AGENTS_MD_FILENAME = "AGENTS.md";
|
|
4936
|
+
async function readAgentsMd(workspaceDir) {
|
|
4937
|
+
try {
|
|
4938
|
+
return await fs15.readFile(path14.join(workspaceDir, AGENTS_MD_FILENAME), "utf8");
|
|
4939
|
+
} catch {
|
|
4940
|
+
return "";
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
async function writeAgentsMd(workspaceDir, content) {
|
|
4944
|
+
await fs15.writeFile(path14.join(workspaceDir, AGENTS_MD_FILENAME), content, "utf8");
|
|
4945
|
+
}
|
|
4946
|
+
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.
|
|
4947
|
+
|
|
4948
|
+
You will receive:
|
|
4949
|
+
1. The current AGENTS.md content (may be empty on first run)
|
|
4950
|
+
2. A summary of what just happened in the latest conversation turn
|
|
4951
|
+
|
|
4952
|
+
Your job: decide if AGENTS.md should be updated based on the new information. If yes, output the FULL updated AGENTS.md content. If nothing meaningful changed, output exactly "NO_UPDATE".
|
|
4953
|
+
|
|
4954
|
+
Rules:
|
|
4955
|
+
- Keep it concise \u2014 under 2000 characters. This gets injected into every system prompt.
|
|
4956
|
+
- Structure: Project overview \u2192 Key files \u2192 Current state \u2192 Research direction
|
|
4957
|
+
- Only include information that helps a NEW agent session pick up where this one left off
|
|
4958
|
+
- Don't include conversation-specific details \u2014 only durable project knowledge
|
|
4959
|
+
- Update incrementally \u2014 preserve existing content, add/modify what changed
|
|
4960
|
+
- Use markdown with ## headings`;
|
|
4961
|
+
var UPDATE_USER_TEMPLATE = `Current AGENTS.md:
|
|
4962
|
+
---
|
|
4963
|
+
{CURRENT_CONTENT}
|
|
4964
|
+
---
|
|
4965
|
+
|
|
4966
|
+
Latest turn summary:
|
|
4967
|
+
User asked: {USER_MESSAGE}
|
|
4968
|
+
Agent did: {AGENT_SUMMARY}
|
|
4969
|
+
|
|
4970
|
+
Should AGENTS.md be updated? If yes, output the full updated content. If no meaningful project-level changes, output exactly "NO_UPDATE".`;
|
|
4971
|
+
async function maybeUpdateAgentsMd(input2) {
|
|
4972
|
+
if (input2.userMessage.length < 15) return false;
|
|
4973
|
+
if (input2.userMessage.startsWith("/")) return false;
|
|
4974
|
+
const currentContent = await readAgentsMd(input2.workspaceDir);
|
|
4975
|
+
const userPrompt = UPDATE_USER_TEMPLATE.replace("{CURRENT_CONTENT}", currentContent || "(empty \u2014 first time)").replace("{USER_MESSAGE}", input2.userMessage.slice(0, 500)).replace("{AGENT_SUMMARY}", input2.agentResponse.slice(0, 1500));
|
|
4976
|
+
try {
|
|
4977
|
+
const response = await input2.provider.callLLM({
|
|
4978
|
+
messages: [
|
|
4979
|
+
{ role: "system", content: UPDATE_SYSTEM_PROMPT },
|
|
4980
|
+
{ role: "user", content: userPrompt }
|
|
4981
|
+
],
|
|
4982
|
+
model: input2.model ?? "gpt-5.4-mini",
|
|
4983
|
+
maxTokens: 2048,
|
|
4984
|
+
temperature: 0
|
|
4985
|
+
});
|
|
4986
|
+
const result = response.content.trim();
|
|
4987
|
+
if (result === "NO_UPDATE" || result.length < 20) {
|
|
4988
|
+
return false;
|
|
4989
|
+
}
|
|
4990
|
+
const cleaned = result.replace(/^```(?:markdown)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
4991
|
+
await writeAgentsMd(input2.workspaceDir, cleaned);
|
|
4992
|
+
return true;
|
|
4993
|
+
} catch {
|
|
4994
|
+
return false;
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
|
|
4916
4998
|
// src/lib/agent/runtime.ts
|
|
4917
4999
|
var TOOL_DESCRIPTIONS = {
|
|
4918
5000
|
read_file: (a) => `Reading ${a.file_path ?? "file"}`,
|
|
@@ -4948,7 +5030,6 @@ function describeToolCall(name, args) {
|
|
|
4948
5030
|
return fn ? fn(args) : `Running ${name}`;
|
|
4949
5031
|
}
|
|
4950
5032
|
function buildSystemPrompt(ctx, activeSkills) {
|
|
4951
|
-
const workspaceMap = ctx.availableKeys.map((key) => `- ${key}${ctx.fileLabels?.[key] ? ` \u2014 ${ctx.fileLabels[key]}` : ""}`).join("\n");
|
|
4952
5033
|
const skillText = activeSkills.map((skill) => `## Active Skill: ${skill.name}
|
|
4953
5034
|
${skill.prompt}`).join("\n\n");
|
|
4954
5035
|
return [
|
|
@@ -4956,8 +5037,9 @@ ${skill.prompt}`).join("\n\n");
|
|
|
4956
5037
|
"",
|
|
4957
5038
|
"## Capabilities",
|
|
4958
5039
|
"You have full access to the local filesystem and shell. You can:",
|
|
4959
|
-
"- Read any file on disk with read_file
|
|
4960
|
-
"- List directories with list_directory to explore
|
|
5040
|
+
"- Read any file on disk with read_file",
|
|
5041
|
+
"- List directories with list_directory to explore the workspace and discover files",
|
|
5042
|
+
"- Search file contents with search_workspace",
|
|
4961
5043
|
"- Run shell commands with run_command (python, R, node, LaTeX, curl, git, etc.)",
|
|
4962
5044
|
"- Write new workspace files or edit existing ones",
|
|
4963
5045
|
"- Search academic papers across OpenAlex, Semantic Scholar, and arXiv",
|
|
@@ -4966,15 +5048,17 @@ ${skill.prompt}`).join("\n\n");
|
|
|
4966
5048
|
"- Activate research skills for specialized workflows",
|
|
4967
5049
|
"",
|
|
4968
5050
|
"## Principles",
|
|
4969
|
-
"-
|
|
5051
|
+
"- Start by exploring. Use list_directory and search_workspace to understand the workspace before acting.",
|
|
5052
|
+
"- Read before writing. Understand existing files before making changes.",
|
|
4970
5053
|
"- Ground claims in sources. Cite papers and data, not assumptions.",
|
|
4971
5054
|
"- Run code to verify. When you write a script, run it and check the output.",
|
|
4972
5055
|
"- Be transparent. Show the user what you're doing and why.",
|
|
4973
5056
|
"- When unsure, ask. Use ask_user rather than guessing.",
|
|
4974
5057
|
"- For large outputs, redirect to a file and read selectively.",
|
|
4975
5058
|
"",
|
|
4976
|
-
|
|
4977
|
-
${
|
|
5059
|
+
`## Workspace
|
|
5060
|
+
Root: ${process.cwd()}
|
|
5061
|
+
Use list_directory to explore. Use search_workspace or read_file to read content.`,
|
|
4978
5062
|
skillText
|
|
4979
5063
|
].filter(Boolean).join("\n");
|
|
4980
5064
|
}
|
|
@@ -5001,7 +5085,14 @@ async function runAgentTurn(input2) {
|
|
|
5001
5085
|
const usage = input2.sessionUsage ?? createSessionUsage();
|
|
5002
5086
|
const memories = await loadMemories({ homeDir: input2.homeDir });
|
|
5003
5087
|
const memoryBlock = formatMemoriesForPrompt(memories);
|
|
5004
|
-
const
|
|
5088
|
+
const agentsMd = input2.workspace.workspaceDir ? await readAgentsMd(input2.workspace.workspaceDir).catch(() => "") : "";
|
|
5089
|
+
const contextBlocks = [
|
|
5090
|
+
systemPrompt,
|
|
5091
|
+
memoryBlock ? memoryBlock : null,
|
|
5092
|
+
agentsMd ? `## Project Context (from AGENTS.md)
|
|
5093
|
+
${agentsMd}` : null
|
|
5094
|
+
].filter(Boolean).join("\n\n");
|
|
5095
|
+
const fullSystemPrompt = contextBlocks;
|
|
5005
5096
|
let messages = [
|
|
5006
5097
|
{ role: "system", content: fullSystemPrompt },
|
|
5007
5098
|
...input2.history,
|
|
@@ -5068,6 +5159,18 @@ async function runAgentTurn(input2) {
|
|
|
5068
5159
|
}
|
|
5069
5160
|
}).catch(() => {
|
|
5070
5161
|
});
|
|
5162
|
+
if (input2.workspace.workspaceDir) {
|
|
5163
|
+
maybeUpdateAgentsMd({
|
|
5164
|
+
workspaceDir: input2.workspace.workspaceDir,
|
|
5165
|
+
userMessage: input2.message,
|
|
5166
|
+
agentResponse: fullText,
|
|
5167
|
+
provider: input2.provider,
|
|
5168
|
+
model: "gpt-5.4-mini"
|
|
5169
|
+
}).then((updated) => {
|
|
5170
|
+
if (updated) input2.onAgentsMdUpdated?.();
|
|
5171
|
+
}).catch(() => {
|
|
5172
|
+
});
|
|
5173
|
+
}
|
|
5071
5174
|
return {
|
|
5072
5175
|
text: fullText,
|
|
5073
5176
|
proposedUpdates,
|
|
@@ -5159,8 +5262,8 @@ function classifyUpdateRisk(update) {
|
|
|
5159
5262
|
}
|
|
5160
5263
|
|
|
5161
5264
|
// src/lib/workspace/apply-update.ts
|
|
5162
|
-
import
|
|
5163
|
-
import
|
|
5265
|
+
import fs16 from "fs/promises";
|
|
5266
|
+
import path15 from "path";
|
|
5164
5267
|
function resolveRelativePath(update) {
|
|
5165
5268
|
if (update.key.startsWith("path:")) {
|
|
5166
5269
|
return update.key.slice(5);
|
|
@@ -5181,20 +5284,20 @@ function resolveRelativePath(update) {
|
|
|
5181
5284
|
}
|
|
5182
5285
|
async function applyProposedUpdate(workspaceDir, update) {
|
|
5183
5286
|
const relativePath = resolveRelativePath(update);
|
|
5184
|
-
const absolutePath =
|
|
5185
|
-
await
|
|
5186
|
-
await
|
|
5287
|
+
const absolutePath = path15.join(workspaceDir, relativePath);
|
|
5288
|
+
await fs16.mkdir(path15.dirname(absolutePath), { recursive: true });
|
|
5289
|
+
await fs16.writeFile(absolutePath, update.content, "utf8");
|
|
5187
5290
|
return absolutePath;
|
|
5188
5291
|
}
|
|
5189
5292
|
|
|
5190
5293
|
// src/lib/workspace/sessions.ts
|
|
5191
|
-
import
|
|
5192
|
-
import
|
|
5294
|
+
import fs17 from "fs/promises";
|
|
5295
|
+
import path16 from "path";
|
|
5193
5296
|
async function appendSessionEvent(workspaceDir, sessionId, event) {
|
|
5194
5297
|
const sessionsDir = getWorkspaceSessionsDir(workspaceDir);
|
|
5195
|
-
await
|
|
5196
|
-
const sessionFile =
|
|
5197
|
-
await
|
|
5298
|
+
await fs17.mkdir(sessionsDir, { recursive: true });
|
|
5299
|
+
const sessionFile = path16.join(sessionsDir, `${sessionId}.jsonl`);
|
|
5300
|
+
await fs17.appendFile(sessionFile, `${JSON.stringify(event)}
|
|
5198
5301
|
`, "utf8");
|
|
5199
5302
|
}
|
|
5200
5303
|
function parseEvents(raw) {
|
|
@@ -5210,7 +5313,7 @@ async function listSessions(workspaceDir) {
|
|
|
5210
5313
|
const sessionsDir = getWorkspaceSessionsDir(workspaceDir);
|
|
5211
5314
|
let files;
|
|
5212
5315
|
try {
|
|
5213
|
-
files = await
|
|
5316
|
+
files = await fs17.readdir(sessionsDir);
|
|
5214
5317
|
} catch {
|
|
5215
5318
|
return [];
|
|
5216
5319
|
}
|
|
@@ -5218,7 +5321,7 @@ async function listSessions(workspaceDir) {
|
|
|
5218
5321
|
for (const file of files) {
|
|
5219
5322
|
if (!file.endsWith(".jsonl")) continue;
|
|
5220
5323
|
const id = file.replace(/\.jsonl$/, "");
|
|
5221
|
-
const raw = await
|
|
5324
|
+
const raw = await fs17.readFile(path16.join(sessionsDir, file), "utf8");
|
|
5222
5325
|
const events = parseEvents(raw);
|
|
5223
5326
|
if (events.length === 0) continue;
|
|
5224
5327
|
const chatTurns = events.filter((e) => e.type === "chat.turn");
|
|
@@ -5239,8 +5342,8 @@ async function listSessions(workspaceDir) {
|
|
|
5239
5342
|
}
|
|
5240
5343
|
async function loadSessionHistory(workspaceDir, sessionId) {
|
|
5241
5344
|
const sessionsDir = getWorkspaceSessionsDir(workspaceDir);
|
|
5242
|
-
const sessionFile =
|
|
5243
|
-
const raw = await
|
|
5345
|
+
const sessionFile = path16.join(sessionsDir, `${sessionId}.jsonl`);
|
|
5346
|
+
const raw = await fs17.readFile(sessionFile, "utf8");
|
|
5244
5347
|
const events = parseEvents(raw);
|
|
5245
5348
|
const messages = [];
|
|
5246
5349
|
const llmHistory = [];
|
|
@@ -5352,15 +5455,15 @@ function ConfigScreen({ items, onUpdate, onClose }) {
|
|
|
5352
5455
|
}
|
|
5353
5456
|
|
|
5354
5457
|
// src/lib/cli/update-check.ts
|
|
5355
|
-
import
|
|
5356
|
-
import
|
|
5458
|
+
import fs18 from "fs/promises";
|
|
5459
|
+
import path17 from "path";
|
|
5357
5460
|
import os4 from "os";
|
|
5358
5461
|
var PACKAGE_NAME = "open-research";
|
|
5359
5462
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
5360
|
-
var STATE_FILE =
|
|
5463
|
+
var STATE_FILE = path17.join(os4.homedir(), ".open-research", "update-check.json");
|
|
5361
5464
|
async function readState() {
|
|
5362
5465
|
try {
|
|
5363
|
-
const raw = await
|
|
5466
|
+
const raw = await fs18.readFile(STATE_FILE, "utf8");
|
|
5364
5467
|
const parsed = JSON.parse(raw);
|
|
5365
5468
|
return {
|
|
5366
5469
|
lastCheck: typeof parsed.lastCheck === "number" ? parsed.lastCheck : 0,
|
|
@@ -5372,8 +5475,8 @@ async function readState() {
|
|
|
5372
5475
|
}
|
|
5373
5476
|
}
|
|
5374
5477
|
async function writeState(state) {
|
|
5375
|
-
await
|
|
5376
|
-
await
|
|
5478
|
+
await fs18.mkdir(path17.dirname(STATE_FILE), { recursive: true });
|
|
5479
|
+
await fs18.writeFile(STATE_FILE, JSON.stringify(state), "utf8");
|
|
5377
5480
|
}
|
|
5378
5481
|
function getCurrentVersion() {
|
|
5379
5482
|
return getPackageVersion();
|
|
@@ -5425,10 +5528,136 @@ async function checkForUpdate() {
|
|
|
5425
5528
|
}
|
|
5426
5529
|
}
|
|
5427
5530
|
|
|
5531
|
+
// src/lib/workspace/init-agents-md.ts
|
|
5532
|
+
import fs19 from "fs/promises";
|
|
5533
|
+
import path18 from "path";
|
|
5534
|
+
var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
|
|
5535
|
+
"node_modules",
|
|
5536
|
+
".git",
|
|
5537
|
+
"dist",
|
|
5538
|
+
"build",
|
|
5539
|
+
".next",
|
|
5540
|
+
"__pycache__",
|
|
5541
|
+
".venv",
|
|
5542
|
+
"venv",
|
|
5543
|
+
".cache",
|
|
5544
|
+
"target",
|
|
5545
|
+
".open-research",
|
|
5546
|
+
"coverage"
|
|
5547
|
+
]);
|
|
5548
|
+
var INTERESTING_FILES = /* @__PURE__ */ new Set([
|
|
5549
|
+
"package.json",
|
|
5550
|
+
"pyproject.toml",
|
|
5551
|
+
"Cargo.toml",
|
|
5552
|
+
"go.mod",
|
|
5553
|
+
"requirements.txt",
|
|
5554
|
+
"setup.py",
|
|
5555
|
+
"setup.cfg",
|
|
5556
|
+
"README.md",
|
|
5557
|
+
"README.txt",
|
|
5558
|
+
"readme.md",
|
|
5559
|
+
"Makefile",
|
|
5560
|
+
"Dockerfile",
|
|
5561
|
+
"docker-compose.yml",
|
|
5562
|
+
".env.example",
|
|
5563
|
+
"tsconfig.json",
|
|
5564
|
+
"vitest.config.ts",
|
|
5565
|
+
"jest.config.js"
|
|
5566
|
+
]);
|
|
5567
|
+
async function scanDirectoryShallow(dir, maxDepth = 2, depth = 0) {
|
|
5568
|
+
const results = [];
|
|
5569
|
+
if (depth > maxDepth) return results;
|
|
5570
|
+
try {
|
|
5571
|
+
const entries = await fs19.readdir(dir, { withFileTypes: true });
|
|
5572
|
+
for (const entry of entries) {
|
|
5573
|
+
if (IGNORED_DIRS2.has(entry.name)) continue;
|
|
5574
|
+
if (entry.name.startsWith(".") && depth === 0 && entry.isDirectory()) continue;
|
|
5575
|
+
const fullPath = path18.join(dir, entry.name);
|
|
5576
|
+
const relativePath = path18.relative(process.cwd(), fullPath);
|
|
5577
|
+
if (entry.isDirectory()) {
|
|
5578
|
+
results.push({ path: relativePath + "/", size: 0, isDir: true });
|
|
5579
|
+
const children = await scanDirectoryShallow(fullPath, maxDepth, depth + 1);
|
|
5580
|
+
results.push(...children);
|
|
5581
|
+
} else {
|
|
5582
|
+
const stat = await fs19.stat(fullPath).catch(() => null);
|
|
5583
|
+
results.push({ path: relativePath, size: stat?.size ?? 0, isDir: false });
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
} catch {
|
|
5587
|
+
}
|
|
5588
|
+
return results;
|
|
5589
|
+
}
|
|
5590
|
+
async function readKeyFiles(dir) {
|
|
5591
|
+
const contents = {};
|
|
5592
|
+
for (const name of INTERESTING_FILES) {
|
|
5593
|
+
const filePath = path18.join(dir, name);
|
|
5594
|
+
try {
|
|
5595
|
+
const content = await fs19.readFile(filePath, "utf8");
|
|
5596
|
+
contents[name] = content.slice(0, 2e3);
|
|
5597
|
+
} catch {
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
return contents;
|
|
5601
|
+
}
|
|
5602
|
+
var INIT_PROMPT = `You are creating an AGENTS.md file for a research workspace. This file will be injected into an AI research agent's system prompt every session to give it instant project context.
|
|
5603
|
+
|
|
5604
|
+
Based on the directory structure and key file contents below, write a concise AGENTS.md that covers:
|
|
5605
|
+
|
|
5606
|
+
## Project Overview
|
|
5607
|
+
What is this project about? What type of research?
|
|
5608
|
+
|
|
5609
|
+
## Structure
|
|
5610
|
+
Key directories and what they contain (only the important ones).
|
|
5611
|
+
|
|
5612
|
+
## Key Files
|
|
5613
|
+
Important files and what they do (only the notable ones).
|
|
5614
|
+
|
|
5615
|
+
## Research Context
|
|
5616
|
+
What research appears to be in progress based on the files?
|
|
5617
|
+
|
|
5618
|
+
## Development
|
|
5619
|
+
How to build/run/test (if applicable, based on package.json or similar).
|
|
5620
|
+
|
|
5621
|
+
Rules:
|
|
5622
|
+
- Keep it under 1500 characters total
|
|
5623
|
+
- Be specific to THIS project, not generic
|
|
5624
|
+
- If it's unclear what the project does, say so and note what you can see
|
|
5625
|
+
- Use markdown with ## headings
|
|
5626
|
+
- Don't include obvious things ("node_modules contains npm packages")`;
|
|
5627
|
+
async function generateInitialAgentsMd(input2) {
|
|
5628
|
+
const dir = input2.workspaceDir;
|
|
5629
|
+
const files = await scanDirectoryShallow(dir);
|
|
5630
|
+
const tree = files.slice(0, 100).map((f) => `${f.isDir ? "d" : "f"} ${f.path}${f.size > 0 ? ` (${(f.size / 1024).toFixed(1)}KB)` : ""}`).join("\n");
|
|
5631
|
+
const keyFiles = await readKeyFiles(dir);
|
|
5632
|
+
const keyFileText = Object.entries(keyFiles).map(([name, content2]) => `### ${name}
|
|
5633
|
+
\`\`\`
|
|
5634
|
+
${content2}
|
|
5635
|
+
\`\`\``).join("\n\n");
|
|
5636
|
+
const userMessage = `Directory: ${dir}
|
|
5637
|
+
|
|
5638
|
+
File tree:
|
|
5639
|
+
${tree}
|
|
5640
|
+
|
|
5641
|
+
${keyFileText ? `Key files:
|
|
5642
|
+
${keyFileText}` : "No recognizable key files found."}`;
|
|
5643
|
+
const response = await input2.provider.callLLM({
|
|
5644
|
+
messages: [
|
|
5645
|
+
{ role: "system", content: INIT_PROMPT },
|
|
5646
|
+
{ role: "user", content: userMessage.slice(0, 3e4) }
|
|
5647
|
+
],
|
|
5648
|
+
model: input2.model ?? "gpt-5.4-mini",
|
|
5649
|
+
maxTokens: 2048,
|
|
5650
|
+
temperature: 0
|
|
5651
|
+
});
|
|
5652
|
+
const content = response.content.replace(/^```(?:markdown)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
5653
|
+
await writeAgentsMd(dir, content);
|
|
5654
|
+
return content;
|
|
5655
|
+
}
|
|
5656
|
+
|
|
5428
5657
|
// src/lib/preview/server.ts
|
|
5429
5658
|
import http2 from "http";
|
|
5430
|
-
import
|
|
5431
|
-
import
|
|
5659
|
+
import fs20 from "fs";
|
|
5660
|
+
import path19 from "path";
|
|
5432
5661
|
|
|
5433
5662
|
// src/lib/preview/latex-to-html.ts
|
|
5434
5663
|
function latexToHtml(latex) {
|
|
@@ -5695,11 +5924,11 @@ var HTML_TEMPLATE = `<!DOCTYPE html>
|
|
|
5695
5924
|
</body>
|
|
5696
5925
|
</html>`;
|
|
5697
5926
|
function startPreviewServer(texPath) {
|
|
5698
|
-
const resolved =
|
|
5927
|
+
const resolved = path19.resolve(texPath);
|
|
5699
5928
|
let currentHash = "";
|
|
5700
5929
|
function getContentHash() {
|
|
5701
5930
|
try {
|
|
5702
|
-
const content =
|
|
5931
|
+
const content = fs20.readFileSync(resolved, "utf8");
|
|
5703
5932
|
return `${content.length}-${content.slice(0, 100)}-${content.slice(-100)}`;
|
|
5704
5933
|
} catch {
|
|
5705
5934
|
return "error";
|
|
@@ -5707,7 +5936,7 @@ function startPreviewServer(texPath) {
|
|
|
5707
5936
|
}
|
|
5708
5937
|
function renderPage() {
|
|
5709
5938
|
try {
|
|
5710
|
-
const latex =
|
|
5939
|
+
const latex = fs20.readFileSync(resolved, "utf8");
|
|
5711
5940
|
const htmlContent = latexToHtml(latex);
|
|
5712
5941
|
currentHash = getContentHash();
|
|
5713
5942
|
return HTML_TEMPLATE.replace("{{CONTENT}}", htmlContent);
|
|
@@ -6466,6 +6695,20 @@ function App({
|
|
|
6466
6695
|
addSystemMessage(`Initialized workspace "${project.title}" at ${target}`);
|
|
6467
6696
|
const scanned = await scanWorkspace(target);
|
|
6468
6697
|
startTransition(() => setWorkspaceFiles(scanned.files));
|
|
6698
|
+
if (hasAuth) {
|
|
6699
|
+
addSystemMessage("Generating AGENTS.md...");
|
|
6700
|
+
try {
|
|
6701
|
+
const provider = await createProviderFromStoredAuth({ homeDir });
|
|
6702
|
+
await generateInitialAgentsMd({
|
|
6703
|
+
workspaceDir: target,
|
|
6704
|
+
provider,
|
|
6705
|
+
model: "gpt-5.4-mini"
|
|
6706
|
+
});
|
|
6707
|
+
addSystemMessage("AGENTS.md created \u2014 project context will be loaded on every session.");
|
|
6708
|
+
} catch {
|
|
6709
|
+
addSystemMessage("AGENTS.md generation skipped (connect auth first).");
|
|
6710
|
+
}
|
|
6711
|
+
}
|
|
6469
6712
|
} catch (err) {
|
|
6470
6713
|
addSystemMessage(`Init failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6471
6714
|
} finally {
|
|
@@ -6983,6 +7226,7 @@ ${msg.text}
|
|
|
6983
7226
|
const provider = await createProviderFromStoredAuth({ homeDir });
|
|
6984
7227
|
const workspace = await scanWorkspace(workspacePath);
|
|
6985
7228
|
const workspaceContext = {
|
|
7229
|
+
workspaceDir: workspacePath,
|
|
6986
7230
|
runId: sessionId,
|
|
6987
7231
|
workspaceFiles: Object.fromEntries(workspace.files.map((f) => [f.key, f.content])),
|
|
6988
7232
|
availableKeys: workspace.files.map((f) => f.key),
|
|
@@ -7115,6 +7359,7 @@ ${msg.text}
|
|
|
7115
7359
|
const provider = await createProviderFromStoredAuth({ homeDir });
|
|
7116
7360
|
const workspace = await scanWorkspace(workspacePath);
|
|
7117
7361
|
const workspaceContext = {
|
|
7362
|
+
workspaceDir: workspacePath,
|
|
7118
7363
|
runId: sessionId,
|
|
7119
7364
|
workspaceFiles: Object.fromEntries(workspace.files.map((f) => [f.key, f.content])),
|
|
7120
7365
|
availableKeys: workspace.files.map((f) => f.key),
|
|
@@ -7460,7 +7705,7 @@ ${msg.text}
|
|
|
7460
7705
|
statusParts,
|
|
7461
7706
|
statusColor,
|
|
7462
7707
|
tokenDisplay,
|
|
7463
|
-
workspaceName: hasWorkspace ?
|
|
7708
|
+
workspaceName: hasWorkspace ? path20.basename(workspacePath) : process.cwd(),
|
|
7464
7709
|
mode: agentMode,
|
|
7465
7710
|
planningStatus: planningState.status
|
|
7466
7711
|
}
|
|
@@ -7472,7 +7717,7 @@ ${msg.text}
|
|
|
7472
7717
|
var program = new Command();
|
|
7473
7718
|
program.name("open-research").version(getPackageVersion()).description("Local-first research CLI powered by ChatGPT/Codex auth.").argument("[workspacePath]", "Optional workspace path to open").action(async (workspacePath) => {
|
|
7474
7719
|
await ensureOpenResearchConfig();
|
|
7475
|
-
const target = workspacePath ?
|
|
7720
|
+
const target = workspacePath ? path21.resolve(workspacePath) : process.cwd();
|
|
7476
7721
|
const project = await loadWorkspaceProject(target);
|
|
7477
7722
|
const auth2 = await loadStoredAuth();
|
|
7478
7723
|
render(
|
|
@@ -7488,7 +7733,7 @@ program.name("open-research").version(getPackageVersion()).description("Local-fi
|
|
|
7488
7733
|
});
|
|
7489
7734
|
program.command("init").argument("[workspacePath]").description("Initialize an Open Research workspace.").action(async (workspacePath) => {
|
|
7490
7735
|
await ensureOpenResearchConfig();
|
|
7491
|
-
const target =
|
|
7736
|
+
const target = path21.resolve(workspacePath ?? process.cwd());
|
|
7492
7737
|
const project = await initWorkspace({ workspaceDir: target });
|
|
7493
7738
|
console.log(`Initialized workspace: ${target}`);
|
|
7494
7739
|
console.log(`Title: ${project.title}`);
|
|
@@ -7557,8 +7802,8 @@ skills.command("create").argument("[name]").description("Scaffold a new user ski
|
|
|
7557
7802
|
});
|
|
7558
7803
|
skills.command("edit").argument("<name>").description("Open a user skill in $EDITOR.").action(async (name) => {
|
|
7559
7804
|
await ensureOpenResearchConfig();
|
|
7560
|
-
const skillDir =
|
|
7561
|
-
openInEditor(
|
|
7805
|
+
const skillDir = path21.join(getOpenResearchSkillsDir(), name);
|
|
7806
|
+
openInEditor(path21.join(skillDir, "SKILL.md"));
|
|
7562
7807
|
const validation = await validateSkillDirectory({ skillDir });
|
|
7563
7808
|
if (!validation.ok) {
|
|
7564
7809
|
console.error(validation.errors.join("\n"));
|
|
@@ -7569,9 +7814,9 @@ skills.command("edit").argument("<name>").description("Open a user skill in $EDI
|
|
|
7569
7814
|
});
|
|
7570
7815
|
skills.command("validate").argument("[nameOrPath]").description("Validate one user skill.").action(async (nameOrPath) => {
|
|
7571
7816
|
await ensureOpenResearchConfig();
|
|
7572
|
-
const skillDir = nameOrPath ?
|
|
7817
|
+
const skillDir = nameOrPath ? path21.isAbsolute(nameOrPath) ? nameOrPath : path21.join(getOpenResearchSkillsDir(), nameOrPath) : getOpenResearchSkillsDir();
|
|
7573
7818
|
const stat = await import("fs/promises").then(
|
|
7574
|
-
(
|
|
7819
|
+
(fs21) => fs21.stat(skillDir).catch(() => null)
|
|
7575
7820
|
);
|
|
7576
7821
|
if (!stat) {
|
|
7577
7822
|
throw new Error(`Skill path not found: ${skillDir}`);
|
package/package.json
CHANGED