open-research 0.1.9 → 0.1.11
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 +342 -145
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -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.11";
|
|
815
815
|
function getPackageVersion() {
|
|
816
816
|
return PACKAGE_VERSION;
|
|
817
817
|
}
|
|
@@ -4761,131 +4761,283 @@ async function manualCompact(messages, model, provider, usage, customInstruction
|
|
|
4761
4761
|
// src/lib/memory/store.ts
|
|
4762
4762
|
import fs14 from "fs/promises";
|
|
4763
4763
|
import path13 from "path";
|
|
4764
|
-
function
|
|
4764
|
+
function getGlobalMemoryFile(options) {
|
|
4765
4765
|
return path13.join(getOpenResearchRoot(options), "memory.json");
|
|
4766
4766
|
}
|
|
4767
|
-
|
|
4768
|
-
|
|
4767
|
+
function getProjectMemoryFile(workspaceDir) {
|
|
4768
|
+
return path13.join(workspaceDir, ".open-research", "memory.json");
|
|
4769
|
+
}
|
|
4770
|
+
async function loadMemoryFile(filePath) {
|
|
4769
4771
|
try {
|
|
4770
|
-
const raw = await fs14.readFile(
|
|
4772
|
+
const raw = await fs14.readFile(filePath, "utf8");
|
|
4771
4773
|
const store = JSON.parse(raw);
|
|
4772
4774
|
return store.memories ?? [];
|
|
4773
4775
|
} catch {
|
|
4774
4776
|
return [];
|
|
4775
4777
|
}
|
|
4776
4778
|
}
|
|
4777
|
-
async function
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
await fs14.writeFile(file, JSON.stringify(store, null, 2), "utf8");
|
|
4779
|
+
async function saveMemoryFile(filePath, memories) {
|
|
4780
|
+
await fs14.mkdir(path13.dirname(filePath), { recursive: true });
|
|
4781
|
+
const store = { version: 2, memories };
|
|
4782
|
+
await fs14.writeFile(filePath, JSON.stringify(store, null, 2), "utf8");
|
|
4782
4783
|
}
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4784
|
+
async function loadGlobalMemories(options) {
|
|
4785
|
+
const mems = await loadMemoryFile(getGlobalMemoryFile(options));
|
|
4786
|
+
return mems.map((m) => ({ ...m, scope: "global" }));
|
|
4787
|
+
}
|
|
4788
|
+
async function loadProjectMemories(workspaceDir) {
|
|
4789
|
+
const mems = await loadMemoryFile(getProjectMemoryFile(workspaceDir));
|
|
4790
|
+
return mems.map((m) => ({ ...m, scope: "project" }));
|
|
4791
|
+
}
|
|
4792
|
+
async function loadAllMemories(options) {
|
|
4793
|
+
const global = await loadGlobalMemories(options);
|
|
4794
|
+
const project = options?.workspaceDir ? await loadProjectMemories(options.workspaceDir) : [];
|
|
4795
|
+
return [...global, ...project];
|
|
4796
|
+
}
|
|
4797
|
+
var MAX_MEMORIES_PER_STORE = 100;
|
|
4798
|
+
function findDuplicate(memories, content) {
|
|
4799
|
+
const b = content.toLowerCase().replace(/\s+/g, " ");
|
|
4800
|
+
const wordsB = new Set(b.split(" ").filter((w) => w.length > 2));
|
|
4801
|
+
return memories.find((m) => {
|
|
4787
4802
|
const a = m.content.toLowerCase().replace(/\s+/g, " ");
|
|
4788
|
-
const
|
|
4789
|
-
const wordsA = new Set(a.split(" "));
|
|
4790
|
-
const wordsB = new Set(b.split(" "));
|
|
4803
|
+
const wordsA = new Set(a.split(" ").filter((w) => w.length > 2));
|
|
4791
4804
|
const intersection = [...wordsA].filter((w) => wordsB.has(w));
|
|
4792
|
-
|
|
4793
|
-
|
|
4805
|
+
return intersection.length / Math.max(wordsA.size, wordsB.size) > 0.7;
|
|
4806
|
+
});
|
|
4807
|
+
}
|
|
4808
|
+
function evictIfNeeded(memories) {
|
|
4809
|
+
if (memories.length <= MAX_MEMORIES_PER_STORE) return;
|
|
4810
|
+
memories.sort((a, b) => {
|
|
4811
|
+
const aScore = new Date(a.lastRelevantAt).getTime() + a.relevanceCount * 864e5;
|
|
4812
|
+
const bScore = new Date(b.lastRelevantAt).getTime() + b.relevanceCount * 864e5;
|
|
4813
|
+
return bScore - aScore;
|
|
4794
4814
|
});
|
|
4815
|
+
memories.length = MAX_MEMORIES_PER_STORE;
|
|
4816
|
+
}
|
|
4817
|
+
async function addMemory(memory, options) {
|
|
4818
|
+
const scope = memory.scope ?? (memory.category === "project" || memory.category === "context" ? "project" : "global");
|
|
4819
|
+
const filePath = scope === "project" && options?.workspaceDir ? getProjectMemoryFile(options.workspaceDir) : getGlobalMemoryFile(options);
|
|
4820
|
+
const memories = await loadMemoryFile(filePath);
|
|
4821
|
+
const existing = findDuplicate(memories, memory.content);
|
|
4795
4822
|
if (existing) {
|
|
4796
4823
|
existing.lastRelevantAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4797
4824
|
existing.relevanceCount++;
|
|
4798
4825
|
if (memory.content.length > existing.content.length) {
|
|
4799
4826
|
existing.content = memory.content;
|
|
4800
4827
|
}
|
|
4801
|
-
await
|
|
4802
|
-
return existing;
|
|
4828
|
+
await saveMemoryFile(filePath, memories);
|
|
4829
|
+
return { ...existing, scope };
|
|
4803
4830
|
}
|
|
4804
4831
|
const newMemory = {
|
|
4805
4832
|
id: crypto.randomUUID(),
|
|
4806
4833
|
content: memory.content,
|
|
4807
4834
|
category: memory.category,
|
|
4835
|
+
scope,
|
|
4808
4836
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4809
4837
|
lastRelevantAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4810
4838
|
relevanceCount: 1
|
|
4811
4839
|
};
|
|
4812
4840
|
memories.push(newMemory);
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
const aScore = new Date(a.lastRelevantAt).getTime() + a.relevanceCount * 864e5;
|
|
4816
|
-
const bScore = new Date(b.lastRelevantAt).getTime() + b.relevanceCount * 864e5;
|
|
4817
|
-
return bScore - aScore;
|
|
4818
|
-
});
|
|
4819
|
-
memories.length = MAX_MEMORIES;
|
|
4820
|
-
}
|
|
4821
|
-
await saveMemories(memories, options);
|
|
4841
|
+
evictIfNeeded(memories);
|
|
4842
|
+
await saveMemoryFile(filePath, memories);
|
|
4822
4843
|
return newMemory;
|
|
4823
4844
|
}
|
|
4824
4845
|
async function deleteMemory(id, options) {
|
|
4825
|
-
const
|
|
4826
|
-
const
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4846
|
+
const globalFile = getGlobalMemoryFile(options);
|
|
4847
|
+
const global = await loadMemoryFile(globalFile);
|
|
4848
|
+
const gIdx = global.findIndex((m) => m.id === id);
|
|
4849
|
+
if (gIdx !== -1) {
|
|
4850
|
+
global.splice(gIdx, 1);
|
|
4851
|
+
await saveMemoryFile(globalFile, global);
|
|
4852
|
+
return true;
|
|
4853
|
+
}
|
|
4854
|
+
if (options?.workspaceDir) {
|
|
4855
|
+
const projectFile = getProjectMemoryFile(options.workspaceDir);
|
|
4856
|
+
const project = await loadMemoryFile(projectFile);
|
|
4857
|
+
const pIdx = project.findIndex((m) => m.id === id);
|
|
4858
|
+
if (pIdx !== -1) {
|
|
4859
|
+
project.splice(pIdx, 1);
|
|
4860
|
+
await saveMemoryFile(projectFile, project);
|
|
4861
|
+
return true;
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
return false;
|
|
4831
4865
|
}
|
|
4832
4866
|
async function clearMemories(options) {
|
|
4833
|
-
|
|
4867
|
+
if (!options?.scope || options.scope === "global") {
|
|
4868
|
+
await saveMemoryFile(getGlobalMemoryFile(options), []);
|
|
4869
|
+
}
|
|
4870
|
+
if (options?.workspaceDir && (!options?.scope || options.scope === "project")) {
|
|
4871
|
+
await saveMemoryFile(getProjectMemoryFile(options.workspaceDir), []);
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
4875
|
+
"the",
|
|
4876
|
+
"a",
|
|
4877
|
+
"an",
|
|
4878
|
+
"is",
|
|
4879
|
+
"are",
|
|
4880
|
+
"was",
|
|
4881
|
+
"were",
|
|
4882
|
+
"be",
|
|
4883
|
+
"been",
|
|
4884
|
+
"being",
|
|
4885
|
+
"have",
|
|
4886
|
+
"has",
|
|
4887
|
+
"had",
|
|
4888
|
+
"do",
|
|
4889
|
+
"does",
|
|
4890
|
+
"did",
|
|
4891
|
+
"will",
|
|
4892
|
+
"would",
|
|
4893
|
+
"could",
|
|
4894
|
+
"should",
|
|
4895
|
+
"may",
|
|
4896
|
+
"might",
|
|
4897
|
+
"can",
|
|
4898
|
+
"shall",
|
|
4899
|
+
"to",
|
|
4900
|
+
"of",
|
|
4901
|
+
"in",
|
|
4902
|
+
"for",
|
|
4903
|
+
"on",
|
|
4904
|
+
"with",
|
|
4905
|
+
"at",
|
|
4906
|
+
"by",
|
|
4907
|
+
"from",
|
|
4908
|
+
"as",
|
|
4909
|
+
"into",
|
|
4910
|
+
"through",
|
|
4911
|
+
"about",
|
|
4912
|
+
"and",
|
|
4913
|
+
"but",
|
|
4914
|
+
"or",
|
|
4915
|
+
"not",
|
|
4916
|
+
"no",
|
|
4917
|
+
"if",
|
|
4918
|
+
"then",
|
|
4919
|
+
"than",
|
|
4920
|
+
"so",
|
|
4921
|
+
"that",
|
|
4922
|
+
"this",
|
|
4923
|
+
"it",
|
|
4924
|
+
"its",
|
|
4925
|
+
"i",
|
|
4926
|
+
"me",
|
|
4927
|
+
"my",
|
|
4928
|
+
"we",
|
|
4929
|
+
"our",
|
|
4930
|
+
"you",
|
|
4931
|
+
"your",
|
|
4932
|
+
"what",
|
|
4933
|
+
"which",
|
|
4934
|
+
"who",
|
|
4935
|
+
"how",
|
|
4936
|
+
"when",
|
|
4937
|
+
"where",
|
|
4938
|
+
"why"
|
|
4939
|
+
]);
|
|
4940
|
+
function tokenize(text) {
|
|
4941
|
+
return text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS.has(w));
|
|
4942
|
+
}
|
|
4943
|
+
function scoreRelevance(memory, queryTokens) {
|
|
4944
|
+
const memTokens = tokenize(memory.content);
|
|
4945
|
+
if (memTokens.length === 0) return 0;
|
|
4946
|
+
let matches = 0;
|
|
4947
|
+
for (const token of memTokens) {
|
|
4948
|
+
if (queryTokens.has(token)) matches++;
|
|
4949
|
+
}
|
|
4950
|
+
const overlapScore = matches / memTokens.length;
|
|
4951
|
+
const categoryBoost = memory.category === "user" ? 0.3 : memory.category === "preference" ? 0.2 : memory.category === "methodology" ? 0.15 : 0;
|
|
4952
|
+
const ageMs = Date.now() - new Date(memory.lastRelevantAt).getTime();
|
|
4953
|
+
const ageDays = ageMs / 864e5;
|
|
4954
|
+
const recencyBoost = Math.max(0, 0.1 - ageDays * 1e-3);
|
|
4955
|
+
const freqBoost = Math.min(0.1, memory.relevanceCount * 0.02);
|
|
4956
|
+
return overlapScore + categoryBoost + recencyBoost + freqBoost;
|
|
4957
|
+
}
|
|
4958
|
+
var MIN_RELEVANCE_SCORE = 0.15;
|
|
4959
|
+
var MAX_INJECTED_MEMORIES = 15;
|
|
4960
|
+
var ALWAYS_INCLUDE_CATEGORIES = ["user", "preference"];
|
|
4961
|
+
function selectRelevantMemories(allMemories, userQuery) {
|
|
4962
|
+
if (allMemories.length === 0) return [];
|
|
4963
|
+
const queryTokens = new Set(tokenize(userQuery));
|
|
4964
|
+
const alwaysInclude = allMemories.filter(
|
|
4965
|
+
(m) => ALWAYS_INCLUDE_CATEGORIES.includes(m.category)
|
|
4966
|
+
);
|
|
4967
|
+
const candidates = allMemories.filter((m) => !ALWAYS_INCLUDE_CATEGORIES.includes(m.category)).map((m) => ({ memory: m, score: scoreRelevance(m, queryTokens) })).filter((c) => c.score >= MIN_RELEVANCE_SCORE).sort((a, b) => b.score - a.score);
|
|
4968
|
+
const selected = [
|
|
4969
|
+
...alwaysInclude.slice(0, 5),
|
|
4970
|
+
// Max 5 identity memories
|
|
4971
|
+
...candidates.slice(0, MAX_INJECTED_MEMORIES - Math.min(alwaysInclude.length, 5)).map((c) => c.memory)
|
|
4972
|
+
];
|
|
4973
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4974
|
+
for (const m of selected) {
|
|
4975
|
+
m.lastRelevantAt = now;
|
|
4976
|
+
}
|
|
4977
|
+
return selected;
|
|
4834
4978
|
}
|
|
4835
4979
|
function formatMemoriesForPrompt(memories) {
|
|
4836
4980
|
if (memories.length === 0) return "";
|
|
4837
|
-
const
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
context: "Context"
|
|
4848
|
-
};
|
|
4849
|
-
for (const [cat, mems] of Object.entries(grouped)) {
|
|
4850
|
-
sections.push(`**${categoryLabels[cat] ?? cat}:**`);
|
|
4851
|
-
for (const m of mems) {
|
|
4852
|
-
sections.push(`- ${m.content}`);
|
|
4853
|
-
}
|
|
4981
|
+
const global = memories.filter((m) => m.scope === "global");
|
|
4982
|
+
const project = memories.filter((m) => m.scope === "project");
|
|
4983
|
+
const sections = ["## Relevant Context"];
|
|
4984
|
+
if (global.length > 0) {
|
|
4985
|
+
sections.push("**About you:**");
|
|
4986
|
+
for (const m of global) sections.push(`- ${m.content}`);
|
|
4987
|
+
}
|
|
4988
|
+
if (project.length > 0) {
|
|
4989
|
+
sections.push("**This project:**");
|
|
4990
|
+
for (const m of project) sections.push(`- ${m.content}`);
|
|
4854
4991
|
}
|
|
4855
4992
|
return sections.join("\n");
|
|
4856
4993
|
}
|
|
4857
4994
|
|
|
4858
4995
|
// src/lib/memory/extractor.ts
|
|
4859
|
-
var EXTRACTION_PROMPT = `You are a memory
|
|
4996
|
+
var EXTRACTION_PROMPT = `You are a memory management system. You decide what to remember about the user across sessions.
|
|
4997
|
+
|
|
4998
|
+
You will receive:
|
|
4999
|
+
1. The current conversation exchange
|
|
5000
|
+
2. ALL existing memories (both global and project-level)
|
|
4860
5001
|
|
|
4861
|
-
|
|
4862
|
-
- Who they are (role, field, institution, expertise level)
|
|
4863
|
-
- What they're working on (current research projects, topics, deadlines)
|
|
4864
|
-
- How they prefer to work (preferred tools, languages, writing style, methodologies)
|
|
4865
|
-
- Methodological preferences (statistical approaches, theoretical frameworks, citation style)
|
|
4866
|
-
- Important context (collaborators, advisors, publication targets, funding constraints)
|
|
5002
|
+
Your job: decide if any NEW memories should be created OR if any existing memories need updating.
|
|
4867
5003
|
|
|
4868
|
-
|
|
4869
|
-
-
|
|
4870
|
-
-
|
|
4871
|
-
-
|
|
4872
|
-
-
|
|
4873
|
-
- If
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
5004
|
+
CRITICAL RULES:
|
|
5005
|
+
- Do NOT create a memory if an existing memory already covers the same fact
|
|
5006
|
+
- If a fact has CHANGED (e.g., user switched from Python to R), output an UPDATE to the existing memory instead of creating a new one
|
|
5007
|
+
- Only create memories for facts useful in FUTURE sessions, not task-specific details
|
|
5008
|
+
- Maximum 3 actions per exchange
|
|
5009
|
+
- If nothing meaningful to remember, return an empty array
|
|
5010
|
+
|
|
5011
|
+
Categories:
|
|
5012
|
+
- "user" \u2014 identity, role, field, institution (\u2192 stored globally)
|
|
5013
|
+
- "preference" \u2014 tools, style, methodology preferences (\u2192 stored globally)
|
|
5014
|
+
- "project" \u2014 current research topics, findings, hypotheses (\u2192 stored per-project)
|
|
5015
|
+
- "methodology" \u2014 statistical approaches, frameworks (\u2192 stored globally)
|
|
5016
|
+
- "context" \u2014 deadlines, collaborators, constraints (\u2192 stored per-project)
|
|
5017
|
+
|
|
5018
|
+
Existing memories:
|
|
4877
5019
|
{EXISTING_MEMORIES}
|
|
4878
5020
|
|
|
4879
|
-
Respond with a JSON array
|
|
5021
|
+
Respond with a JSON array. Each item has:
|
|
5022
|
+
- "action": "create" or "update"
|
|
5023
|
+
- "content": the memory text (for create: new content; for update: the updated content)
|
|
5024
|
+
- "category": one of the categories above
|
|
5025
|
+
- "updateId": (only for "update") the ID of the existing memory to update
|
|
4880
5026
|
|
|
4881
|
-
|
|
4882
|
-
|
|
5027
|
+
If nothing to do, respond with [].
|
|
5028
|
+
|
|
5029
|
+
Example:
|
|
5030
|
+
[{"action": "create", "content": "PhD student in neuroscience at MIT", "category": "user"}]
|
|
5031
|
+
[{"action": "update", "updateId": "abc123", "content": "Now using R instead of Python for analysis", "category": "preference"}]`;
|
|
4883
5032
|
async function extractMemories(input2) {
|
|
4884
|
-
const existing = await
|
|
5033
|
+
const existing = await loadAllMemories({
|
|
5034
|
+
homeDir: input2.homeDir,
|
|
5035
|
+
workspaceDir: input2.workspaceDir
|
|
5036
|
+
});
|
|
4885
5037
|
if (input2.userMessage.startsWith("/") || input2.userMessage.length < 20) {
|
|
4886
5038
|
return [];
|
|
4887
5039
|
}
|
|
4888
|
-
const existingList = existing.length > 0 ? existing.map((m) => `- [${m.category}] ${m.content}`).join("\n") : "(none)";
|
|
5040
|
+
const existingList = existing.length > 0 ? existing.map((m) => `- [${m.scope}/${m.category}] (id: ${m.id.slice(0, 8)}) ${m.content}`).join("\n") : "(none)";
|
|
4889
5041
|
const prompt2 = EXTRACTION_PROMPT.replace("{EXISTING_MEMORIES}", existingList);
|
|
4890
5042
|
const conversationSnippet = [
|
|
4891
5043
|
`User: ${input2.userMessage.slice(0, 2e3)}`,
|
|
@@ -4907,10 +5059,12 @@ async function extractMemories(input2) {
|
|
|
4907
5059
|
if (!Array.isArray(parsed)) return [];
|
|
4908
5060
|
const valid = [];
|
|
4909
5061
|
for (const item of parsed) {
|
|
4910
|
-
if (typeof item.content === "string" && item.content.length > 5 && ["user", "preference", "project", "methodology", "context"].includes(item.category)) {
|
|
5062
|
+
if (typeof item.content === "string" && item.content.length > 5 && ["user", "preference", "project", "methodology", "context"].includes(item.category) && ["create", "update"].includes(item.action)) {
|
|
4911
5063
|
valid.push({
|
|
5064
|
+
action: item.action,
|
|
4912
5065
|
content: item.content,
|
|
4913
|
-
category: item.category
|
|
5066
|
+
category: item.category,
|
|
5067
|
+
updateId: typeof item.updateId === "string" ? item.updateId : void 0
|
|
4914
5068
|
});
|
|
4915
5069
|
}
|
|
4916
5070
|
}
|
|
@@ -4920,13 +5074,35 @@ async function extractMemories(input2) {
|
|
|
4920
5074
|
}
|
|
4921
5075
|
}
|
|
4922
5076
|
async function extractAndStoreMemories(input2) {
|
|
4923
|
-
const
|
|
4924
|
-
const
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
5077
|
+
const actions = await extractMemories(input2);
|
|
5078
|
+
const results = [];
|
|
5079
|
+
const existing = await loadAllMemories({
|
|
5080
|
+
homeDir: input2.homeDir,
|
|
5081
|
+
workspaceDir: input2.workspaceDir
|
|
5082
|
+
});
|
|
5083
|
+
for (const action of actions) {
|
|
5084
|
+
if (action.action === "update" && action.updateId) {
|
|
5085
|
+
const target = existing.find((m) => m.id.startsWith(action.updateId));
|
|
5086
|
+
if (target) {
|
|
5087
|
+
target.content = action.content;
|
|
5088
|
+
target.lastRelevantAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5089
|
+
target.relevanceCount++;
|
|
5090
|
+
const saved = await addMemory(
|
|
5091
|
+
{ content: action.content, category: action.category, scope: target.scope },
|
|
5092
|
+
{ homeDir: input2.homeDir, workspaceDir: input2.workspaceDir }
|
|
5093
|
+
);
|
|
5094
|
+
results.push(saved);
|
|
5095
|
+
}
|
|
5096
|
+
} else {
|
|
5097
|
+
const scope = action.category === "project" || action.category === "context" ? "project" : "global";
|
|
5098
|
+
const saved = await addMemory(
|
|
5099
|
+
{ content: action.content, category: action.category, scope },
|
|
5100
|
+
{ homeDir: input2.homeDir, workspaceDir: input2.workspaceDir }
|
|
5101
|
+
);
|
|
5102
|
+
results.push(saved);
|
|
5103
|
+
}
|
|
4928
5104
|
}
|
|
4929
|
-
return
|
|
5105
|
+
return results;
|
|
4930
5106
|
}
|
|
4931
5107
|
|
|
4932
5108
|
// src/lib/workspace/agents-md.ts
|
|
@@ -5083,16 +5259,19 @@ async function runAgentTurn(input2) {
|
|
|
5083
5259
|
const systemPrompt = isPlanning ? buildPlanningSystemPrompt(input2.workspace, activeSkills) : buildSystemPrompt(input2.workspace, activeSkills);
|
|
5084
5260
|
const model = input2.model ?? "gpt-5.4";
|
|
5085
5261
|
const usage = input2.sessionUsage ?? createSessionUsage();
|
|
5086
|
-
const
|
|
5087
|
-
|
|
5262
|
+
const allMemories = await loadAllMemories({
|
|
5263
|
+
homeDir: input2.homeDir,
|
|
5264
|
+
workspaceDir: input2.workspace.workspaceDir
|
|
5265
|
+
});
|
|
5266
|
+
const relevantMemories = selectRelevantMemories(allMemories, input2.message);
|
|
5267
|
+
const memoryBlock = formatMemoriesForPrompt(relevantMemories);
|
|
5088
5268
|
const agentsMd = input2.workspace.workspaceDir ? await readAgentsMd(input2.workspace.workspaceDir).catch(() => "") : "";
|
|
5089
|
-
const
|
|
5269
|
+
const fullSystemPrompt = [
|
|
5090
5270
|
systemPrompt,
|
|
5091
|
-
memoryBlock
|
|
5271
|
+
memoryBlock || null,
|
|
5092
5272
|
agentsMd ? `## Project Context (from AGENTS.md)
|
|
5093
5273
|
${agentsMd}` : null
|
|
5094
5274
|
].filter(Boolean).join("\n\n");
|
|
5095
|
-
const fullSystemPrompt = contextBlocks;
|
|
5096
5275
|
let messages = [
|
|
5097
5276
|
{ role: "system", content: fullSystemPrompt },
|
|
5098
5277
|
...input2.history,
|
|
@@ -5152,7 +5331,8 @@ ${agentsMd}` : null
|
|
|
5152
5331
|
agentResponse: fullText,
|
|
5153
5332
|
provider: input2.provider,
|
|
5154
5333
|
model: "gpt-5.4-mini",
|
|
5155
|
-
homeDir: input2.homeDir
|
|
5334
|
+
homeDir: input2.homeDir,
|
|
5335
|
+
workspaceDir: input2.workspace.workspaceDir
|
|
5156
5336
|
}).then((stored) => {
|
|
5157
5337
|
if (stored.length > 0) {
|
|
5158
5338
|
input2.onMemoryExtracted?.(stored.map((m) => m.content));
|
|
@@ -5562,7 +5742,8 @@ var INTERESTING_FILES = /* @__PURE__ */ new Set([
|
|
|
5562
5742
|
".env.example",
|
|
5563
5743
|
"tsconfig.json",
|
|
5564
5744
|
"vitest.config.ts",
|
|
5565
|
-
"jest.config.js"
|
|
5745
|
+
"jest.config.js",
|
|
5746
|
+
"AGENTS.md"
|
|
5566
5747
|
]);
|
|
5567
5748
|
async function scanDirectoryShallow(dir, maxDepth = 2, depth = 0) {
|
|
5568
5749
|
const results = [];
|
|
@@ -5573,7 +5754,7 @@ async function scanDirectoryShallow(dir, maxDepth = 2, depth = 0) {
|
|
|
5573
5754
|
if (IGNORED_DIRS2.has(entry.name)) continue;
|
|
5574
5755
|
if (entry.name.startsWith(".") && depth === 0 && entry.isDirectory()) continue;
|
|
5575
5756
|
const fullPath = path18.join(dir, entry.name);
|
|
5576
|
-
const relativePath = path18.relative(
|
|
5757
|
+
const relativePath = path18.relative(dir, fullPath);
|
|
5577
5758
|
if (entry.isDirectory()) {
|
|
5578
5759
|
results.push({ path: relativePath + "/", size: 0, isDir: true });
|
|
5579
5760
|
const children = await scanDirectoryShallow(fullPath, maxDepth, depth + 1);
|
|
@@ -5590,40 +5771,46 @@ async function scanDirectoryShallow(dir, maxDepth = 2, depth = 0) {
|
|
|
5590
5771
|
async function readKeyFiles(dir) {
|
|
5591
5772
|
const contents = {};
|
|
5592
5773
|
for (const name of INTERESTING_FILES) {
|
|
5593
|
-
const filePath = path18.join(dir, name);
|
|
5594
5774
|
try {
|
|
5595
|
-
const content = await fs19.readFile(
|
|
5775
|
+
const content = await fs19.readFile(path18.join(dir, name), "utf8");
|
|
5596
5776
|
contents[name] = content.slice(0, 2e3);
|
|
5597
5777
|
} catch {
|
|
5598
5778
|
}
|
|
5599
5779
|
}
|
|
5600
5780
|
return contents;
|
|
5601
5781
|
}
|
|
5602
|
-
var
|
|
5782
|
+
var CREATE_PROMPT = `You are creating an AGENTS.md file for a research workspace. This file is injected into an AI research agent's system prompt every session to give it instant project context.
|
|
5603
5783
|
|
|
5604
|
-
|
|
5784
|
+
Write a concise AGENTS.md covering:
|
|
5785
|
+
## Project Overview \u2014 What is this project? What research?
|
|
5786
|
+
## Structure \u2014 Key directories and their purpose (only important ones)
|
|
5787
|
+
## Key Files \u2014 Notable files and what they do
|
|
5788
|
+
## Research Context \u2014 What research is in progress?
|
|
5789
|
+
## Development \u2014 How to build/run/test (if applicable)
|
|
5605
5790
|
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
##
|
|
5610
|
-
|
|
5791
|
+
Rules:
|
|
5792
|
+
- Under 1500 characters. This goes into every system prompt.
|
|
5793
|
+
- Specific to THIS project. No generic advice.
|
|
5794
|
+
- Markdown with ## headings.`;
|
|
5795
|
+
var UPDATE_PROMPT = `You are updating an existing AGENTS.md file for a research workspace. This file is injected into an AI research agent's system prompt every session.
|
|
5611
5796
|
|
|
5612
|
-
|
|
5613
|
-
|
|
5797
|
+
You have:
|
|
5798
|
+
1. The CURRENT AGENTS.md content
|
|
5799
|
+
2. A fresh scan of the workspace directory and key files
|
|
5614
5800
|
|
|
5615
|
-
|
|
5616
|
-
|
|
5801
|
+
Your job: compare the current AGENTS.md against the actual workspace state. Update it to reflect reality:
|
|
5802
|
+
- Add new directories/files that appeared
|
|
5803
|
+
- Remove references to things that no longer exist
|
|
5804
|
+
- Update descriptions that are now outdated
|
|
5805
|
+
- Preserve any manually-added notes or context the user wrote
|
|
5806
|
+
- Keep the same ## heading structure
|
|
5617
5807
|
|
|
5618
|
-
|
|
5619
|
-
How to build/run/test (if applicable, based on package.json or similar).
|
|
5808
|
+
If AGENTS.md is already accurate, output it unchanged.
|
|
5620
5809
|
|
|
5621
5810
|
Rules:
|
|
5622
|
-
-
|
|
5623
|
-
-
|
|
5624
|
-
-
|
|
5625
|
-
- Use markdown with ## headings
|
|
5626
|
-
- Don't include obvious things ("node_modules contains npm packages")`;
|
|
5811
|
+
- Under 1500 characters. This goes into every system prompt.
|
|
5812
|
+
- Output the FULL updated AGENTS.md content, not a diff.
|
|
5813
|
+
- Markdown with ## headings.`;
|
|
5627
5814
|
async function generateInitialAgentsMd(input2) {
|
|
5628
5815
|
const dir = input2.workspaceDir;
|
|
5629
5816
|
const files = await scanDirectoryShallow(dir);
|
|
@@ -5633,17 +5820,32 @@ async function generateInitialAgentsMd(input2) {
|
|
|
5633
5820
|
\`\`\`
|
|
5634
5821
|
${content2}
|
|
5635
5822
|
\`\`\``).join("\n\n");
|
|
5636
|
-
const
|
|
5823
|
+
const scanData = `Directory: ${dir}
|
|
5637
5824
|
|
|
5638
5825
|
File tree:
|
|
5639
5826
|
${tree}
|
|
5640
5827
|
|
|
5641
|
-
${keyFileText
|
|
5642
|
-
|
|
5828
|
+
${keyFileText || "No recognizable key files found."}`;
|
|
5829
|
+
const existing = await readAgentsMd(dir);
|
|
5830
|
+
let systemPrompt;
|
|
5831
|
+
let userMessage;
|
|
5832
|
+
if (existing) {
|
|
5833
|
+
systemPrompt = UPDATE_PROMPT;
|
|
5834
|
+
userMessage = `Current AGENTS.md:
|
|
5835
|
+
---
|
|
5836
|
+
${existing}
|
|
5837
|
+
---
|
|
5838
|
+
|
|
5839
|
+
Fresh workspace scan:
|
|
5840
|
+
${scanData.slice(0, 25e3)}`;
|
|
5841
|
+
} else {
|
|
5842
|
+
systemPrompt = CREATE_PROMPT;
|
|
5843
|
+
userMessage = scanData.slice(0, 25e3);
|
|
5844
|
+
}
|
|
5643
5845
|
const response = await input2.provider.callLLM({
|
|
5644
5846
|
messages: [
|
|
5645
|
-
{ role: "system", content:
|
|
5646
|
-
{ role: "user", content: userMessage
|
|
5847
|
+
{ role: "system", content: systemPrompt },
|
|
5848
|
+
{ role: "user", content: userMessage }
|
|
5647
5849
|
],
|
|
5648
5850
|
model: input2.model ?? "gpt-5.4-mini",
|
|
5649
5851
|
maxTokens: 2048,
|
|
@@ -6683,37 +6885,32 @@ function App({
|
|
|
6683
6885
|
}
|
|
6684
6886
|
case "init": {
|
|
6685
6887
|
const target = process.cwd();
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6888
|
+
setBusy(true);
|
|
6889
|
+
try {
|
|
6890
|
+
const existing = await loadWorkspaceProject(target);
|
|
6891
|
+
if (!existing) {
|
|
6892
|
+
await initWorkspace({ workspaceDir: target });
|
|
6893
|
+
addSystemMessage(`Workspace initialized at ${target}`);
|
|
6894
|
+
}
|
|
6689
6895
|
setWorkspacePath(target);
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
const project = await initWorkspace({ workspaceDir: target });
|
|
6694
|
-
setWorkspacePath(target);
|
|
6695
|
-
addSystemMessage(`Initialized workspace "${project.title}" at ${target}`);
|
|
6696
|
-
const scanned = await scanWorkspace(target);
|
|
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
|
-
}
|
|
6712
|
-
} catch (err) {
|
|
6713
|
-
addSystemMessage(`Init failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6714
|
-
} finally {
|
|
6715
|
-
setBusy(false);
|
|
6896
|
+
if (!hasAuth) {
|
|
6897
|
+
addSystemMessage("Run /auth first \u2014 AGENTS.md generation requires auth.");
|
|
6898
|
+
break;
|
|
6716
6899
|
}
|
|
6900
|
+
addSystemMessage("Scanning workspace and updating AGENTS.md...");
|
|
6901
|
+
const provider = await createProviderFromStoredAuth({ homeDir });
|
|
6902
|
+
const result = await generateInitialAgentsMd({
|
|
6903
|
+
workspaceDir: target,
|
|
6904
|
+
provider,
|
|
6905
|
+
model: "gpt-5.4-mini"
|
|
6906
|
+
});
|
|
6907
|
+
addSystemMessage("AGENTS.md ready. Project context will load on every session.");
|
|
6908
|
+
const scanned = await scanWorkspace(target);
|
|
6909
|
+
startTransition(() => setWorkspaceFiles(scanned.files));
|
|
6910
|
+
} catch (err) {
|
|
6911
|
+
addSystemMessage(`Init failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6912
|
+
} finally {
|
|
6913
|
+
setBusy(false);
|
|
6717
6914
|
}
|
|
6718
6915
|
break;
|
|
6719
6916
|
}
|
package/package.json
CHANGED