@x-otto/memory 0.0.1-alpha.2 → 0.0.1-alpha.3
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 +7 -5
- package/dist/index.d.ts +284 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -53
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createLogger as e}from"@x-otto/shared";import{normalizeEnvFraction as t}from"@x-otto/env";import{CONVERSATION_SUMMARY_PREFIX as n}from"@x-otto/interchange";import{existsSync as r,statSync as i}from"node:fs";import{basename as a,dirname as o,extname as s,join as c,relative as l}from"node:path";import{readdir as u,stat as d}from"node:fs/promises";import{
|
|
1
|
+
import{createLogger as e}from"@x-otto/shared";import{normalizeEnvFraction as t}from"@x-otto/env";import{CONVERSATION_SUMMARY_PREFIX as n}from"@x-otto/interchange";import{existsSync as r,statSync as i}from"node:fs";import{basename as a,dirname as o,extname as s,join as c,relative as l}from"node:path";import{readdir as u,stat as d}from"node:fs/promises";import{FileAppendLog as f,createPersistence as p,saveDocument as m}from"@x-otto/persistence";const h=`You are a conversation summarizer for an AI coding assistant. Your task is to create a structured summary of the conversation that preserves all information needed to continue the work.
|
|
2
2
|
|
|
3
3
|
Create the summary in the following format:
|
|
4
4
|
|
|
@@ -36,7 +36,7 @@ IMPORTANT:
|
|
|
36
36
|
- Preserve exact file paths, function names, error messages, and code snippets
|
|
37
37
|
- Keep technical details precise — do not generalize
|
|
38
38
|
- Include all tool call results that affect the current state
|
|
39
|
-
- Note any pending or failed operations`,
|
|
39
|
+
- Note any pending or failed operations`,g=`You are updating an existing conversation summary with new information. The existing summary and new messages are provided below.
|
|
40
40
|
|
|
41
41
|
Produce a single UPDATED summary that stays BOUNDED — a summary that grows without limit across rounds eventually gets hard-truncated by the output cap, silently losing whatever fell past the limit. Keep it tight by actively triaging, not by appending.
|
|
42
42
|
|
|
@@ -55,7 +55,7 @@ Rules:
|
|
|
55
55
|
- Move completed "In Progress" items to "Done", then compress "Done" per the above
|
|
56
56
|
- Merge new work items, decisions, and context; do not simply append
|
|
57
57
|
- Keep the same structured format (including "All User Requests")
|
|
58
|
-
- If any information conflicts, use the newer version`,
|
|
58
|
+
- If any information conflicts, use the newer version`,_=`You are summarizing the FIRST PART of an assistant turn that was split during conversation compaction. The remaining part of this turn is still in the conversation.
|
|
59
59
|
|
|
60
60
|
Create a brief summary in this format:
|
|
61
61
|
|
|
@@ -68,7 +68,7 @@ Create a brief summary in this format:
|
|
|
68
68
|
## Context for Remaining Messages
|
|
69
69
|
- [Information needed to understand the remaining suffix messages]
|
|
70
70
|
|
|
71
|
-
Keep it concise — this will be prepended to the remaining messages of this turn.`,
|
|
71
|
+
Keep it concise — this will be prepended to the remaining messages of this turn.`,v=`## Goal
|
|
72
72
|
[What is the user trying to accomplish?]
|
|
73
73
|
|
|
74
74
|
## Constraints & Preferences
|
|
@@ -96,60 +96,50 @@ Keep it concise — this will be prepended to the remaining messages of this tur
|
|
|
96
96
|
- [Files that were modified]
|
|
97
97
|
|
|
98
98
|
## Critical Context
|
|
99
|
-
- [Any critical context needed to continue the work]`,
|
|
99
|
+
- [Any critical context needed to continue the work]`,y=`IMPORTANT:
|
|
100
100
|
- Preserve exact file paths, function names, error messages, and code snippets
|
|
101
101
|
- Keep technical details precise — do not generalize
|
|
102
102
|
- Include all tool call results that affect the current state
|
|
103
|
-
- Note any pending or failed operations`,
|
|
103
|
+
- Note any pending or failed operations`,b=`You are a conversation summarizer for an AI coding assistant. You are summarizing the EARLIER portion of a conversation. The most recent messages are kept verbatim and will follow your summary unchanged — they are shown to you below in <preserved_tail_context> FOR REFERENCE ONLY.
|
|
104
104
|
|
|
105
105
|
Your summary and those preserved recent messages together become the assistant's full context going forward.
|
|
106
106
|
|
|
107
107
|
Create the summary in the following format:
|
|
108
108
|
|
|
109
|
-
${
|
|
109
|
+
${v}
|
|
110
110
|
|
|
111
111
|
## Context the Preserved Tail Depends On
|
|
112
112
|
- [Facts a reader MUST know to correctly understand the preserved recent messages shown in <preserved_tail_context>: decisions already made, file paths and their current state, conventions/constraints agreed earlier, mistakes already corrected, and any partially-done work those recent messages continue. Read the preserved tail, then look BACK through the earlier conversation for what it silently relies on. This section is the whole point of this summary — be specific.]
|
|
113
113
|
|
|
114
|
-
${
|
|
114
|
+
${y}
|
|
115
115
|
- Do NOT summarize the content of <preserved_tail_context> — it is kept verbatim after your summary and re-describing it wastes your budget. Use it ONLY to work out which parts of the EARLIER conversation it depends on.
|
|
116
|
-
- Summarize ONLY the conversation inside <conversation>.`,
|
|
116
|
+
- Summarize ONLY the conversation inside <conversation>.`,x=`You are summarizing ONE SEGMENT of a longer conversation for an AI coding assistant. This is a partial view — you are NOT seeing the whole conversation, and other segments are being summarized separately. Your segment summary will later be merged with the others.
|
|
117
117
|
|
|
118
118
|
Create a concise summary of THIS SEGMENT in the following format:
|
|
119
119
|
|
|
120
|
-
${_}
|
|
121
|
-
|
|
122
120
|
${v}
|
|
121
|
+
|
|
122
|
+
${y}
|
|
123
123
|
- Do NOT emit conclusions that require seeing the whole conversation (e.g. a single global "next step"). Report only what THIS segment establishes; the merge step will reconcile across segments.
|
|
124
|
-
- If <preserved_tail_context> is present, it holds the most recent messages, kept verbatim outside this compaction. Do NOT summarize it — use it ONLY to judge which parts of THIS segment it depends on, and make sure those parts survive into your summary. Summarize ONLY the content inside <conversation>.`,
|
|
124
|
+
- If <preserved_tail_context> is present, it holds the most recent messages, kept verbatim outside this compaction. Do NOT summarize it — use it ONLY to judge which parts of THIS segment it depends on, and make sure those parts survive into your summary. Summarize ONLY the content inside <conversation>.`,S=`You are merging several segment summaries of one conversation into a single coherent summary for an AI coding assistant. Each input below is a summary of a different (chronological) segment of the same conversation.
|
|
125
125
|
|
|
126
126
|
Reconcile them into one summary in the following format:
|
|
127
127
|
|
|
128
|
-
${_}
|
|
129
|
-
|
|
130
128
|
${v}
|
|
129
|
+
|
|
130
|
+
${y}
|
|
131
131
|
- Resolve conflicts in favour of the LATER segment.
|
|
132
|
-
- Merge duplicate items; do not simply concatenate.`;function
|
|
133
|
-
`)}function
|
|
134
|
-
`)}function w(e){return`${
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
-
|
|
143
|
-
- Key tool parameters (API endpoints, non-sensitive config)
|
|
144
|
-
- Project-specific build/test commands
|
|
145
|
-
|
|
146
|
-
**Should NOT remember**:
|
|
147
|
-
- Temporary task details
|
|
148
|
-
- One-off Q&A
|
|
149
|
-
- API keys, passwords, or sensitive information
|
|
150
|
-
- Casual conversation
|
|
151
|
-
</memory_guidelines>`),n.join(`
|
|
152
|
-
`)}const re=`You are a learning extractor for an AI coding assistant. Read the conversation below and extract durable, cross-session "lessons" — operational knowledge that will help the assistant do better on FUTURE, unrelated sessions in this project.
|
|
132
|
+
- Merge duplicate items; do not simply concatenate.`;function C(e,t,n,r){let i=[];return r?.variant===`segment`?i.push(x):t?(i.push(g),i.push(`\n<existing_summary>\n${t}\n</existing_summary>`),r?.preservedCount&&r.preservedCount>0&&i.push(ee(r.preservedCount))):r?.variant===`preserved-tail`?i.push(b):i.push(h),n&&i.push(`\n<additional_instructions>\n${n}\n</additional_instructions>`),r?.preservedTailReference&&i.push(`\n<preserved_tail_context>\n${r.preservedTailReference}\n</preserved_tail_context>`),r?.omitConversation||i.push(`\n<conversation>\n${e}\n</conversation>`),i.join(`
|
|
133
|
+
`)}function ee(e){return`\nNOTE: The ${e} most recent message(s) are kept verbatim and will follow your summary — do NOT re-summarize them; focus on the earlier conversation and what those recent messages depend on.`}function te(e,t,n){let r=[S];return t&&r.push(`\n<existing_summary>\n${t}\n</existing_summary>`),n&&r.push(`\n<additional_instructions>\n${n}\n</additional_instructions>`),r.push(`\n<segment_summaries>\n${e}\n</segment_summaries>`),r.join(`
|
|
134
|
+
`)}function w(e){return`${_}\n\n<turn_prefix>\n${e}\n</turn_prefix>`}const T=8e3;function E(e){let t=0;for(let n of e)/[\u3000-\u9fff\uff00-\uffef]/.test(n)&&t++;return Math.ceil(t+(e.length-t)/4)}function ne(e,t){if(t<=0)return``;if(E(e)<=t)return e;let n=Array.from(e),r=0,i=n.length,a=``;for(;r<=i;){let e=Math.floor((r+i)/2),o=n.slice(0,e).join(``);E(o)<=t?(a=o,r=e+1):i=e-1}return a}function re(e){return e.endsWith(`/MEMORY.md`)}function ie(e,t,n){if(!t||t.size===0)return{text:e,folded:0};let r=[],i=0,a=n-12096e5;for(let n of e.split(`
|
|
135
|
+
`)){let e=n.match(/^-\s*\[[^\]]+\]\(memory\/([^)]+\.md)\)/);if(!e){r.push(n);continue}let o=t.get(`memory/${e[1]}`);o!==void 0&&o>=a?r.push(n):i++}return i>0&&r.push(`+ ${i} older entr${i>1?`ies`:`y`} — use memory_read to list/fetch`),{text:r.join(`
|
|
136
|
+
`),folded:i}}function ae(e,t=T,n,r=Date.now()){if(e.size===0)return``;let i=[`<agent_memory>`],a=0,o=0;for(let[s,c]of e){let e=(re(s)?ie(c,n,r).text:c).trim();if(!e)continue;let l=t-a;if(l<=0){o++;continue}let u=E(e);if(u<=l)i.push(`# ${s}`),i.push(e),i.push(``),a+=u;else{let n=ne(e,l);i.push(`# ${s}`),i.push(n),i.push(`\n> ⚠ [truncated — content exceeded the memory injection budget (${t} tokens); remainder omitted]`),i.push(``),a=t}}return o>0&&(i.push(`> ⚠ [${o} additional memory source(s) omitted — total injection budget (${t} tokens) reached]`),i.push(``)),i.push(`</agent_memory>`),i.push(``),i.push(`<memory_guidelines>
|
|
137
|
+
Persist durable facts via the memory_record tool — never edit/write .otto state files directly (protected; RFC-228). Scopes: 'workspace' (default, portable facts), 'local' (machine-specific, never synced), 'project-shared' (team-visible AGENTS.md conventions).
|
|
138
|
+
|
|
139
|
+
Remember: user preferences and constraints; patterns learned from feedback; key non-sensitive tool parameters; project-specific build/test commands.
|
|
140
|
+
Do NOT remember: temporary task details; one-off Q&A; API keys, passwords, or sensitive information; casual conversation.
|
|
141
|
+
</memory_guidelines>`),i.join(`
|
|
142
|
+
`)}const oe=`You are a learning extractor for an AI coding assistant. Read the conversation below and extract durable, cross-session "lessons" — operational knowledge that will help the assistant do better on FUTURE, unrelated sessions in this project.
|
|
153
143
|
|
|
154
144
|
A lesson captures a reusable cause→action:
|
|
155
145
|
- "trigger": the situation/context that should recall this lesson (when it applies)
|
|
@@ -169,37 +159,41 @@ DO NOT save these (they pollute recall):
|
|
|
169
159
|
- Secrets, API keys, passwords, tokens, or any sensitive value
|
|
170
160
|
|
|
171
161
|
Output ONLY a JSON array, nothing else. Each element: {"trigger": string, "insight": string, "tags": string[]}.
|
|
172
|
-
If there is nothing durable worth saving, output exactly: []`;function
|
|
162
|
+
If there is nothing durable worth saving, output exactly: []`;function se(e){return`${oe}\n\n<conversation>\n${e}\n</conversation>`}function ce(e,t){return`Earlier conversation history has been compacted into the summary below. If you need details the summary does not cover, call the memory_archive_read tool with archive id "${e}" to pull back the original messages.
|
|
173
163
|
|
|
174
164
|
<summary>
|
|
175
165
|
${t}
|
|
176
|
-
</summary>`}function
|
|
177
|
-
`)
|
|
178
|
-
`)
|
|
166
|
+
</summary>`}function le(e){return`[Low-fidelity summary — only about ${Math.max(0,Math.round(e*100))}% of the original history could be fed into summarization. Significant details are missing. Treat this summary as incomplete: if you need details it does not cover, call memory_archive_read with the archive id below to pull back the original messages, rather than guessing or asking the user to repeat themselves.]`}const D=e(`@x-otto/memory:persistent`),ue=[{path:`~/.otto/AGENTS.md`,writable:!0},{path:`./.otto/AGENTS.md`,writable:!0},{path:`./AGENTS.md`,writable:!1}],de=new Set([`## 代码风格与命名约定`,`## 发布与包管理注意事项`,`## 安全与配置建议`]);function fe(e){let t=e.split(`
|
|
167
|
+
`),n=[],r=!1;for(let e of t){if(e.match(/^## (.+)/)){r=de.has(e.trim()),r||n.push(e);continue}r||n.push(e)}return n.join(`
|
|
168
|
+
`)}const pe=/<!-- otto-memory:start slug="([^"]+)" -->/g;function me(e){let t=new Set;for(let n of e.matchAll(pe))t.add(n[1]??``);return t}function he(e){let t=new Set;for(let n of e.matchAll(/<!-- otto-memory:start slug="[^"]+" -->\n## ([^\n]+)/g))t.add((n[1]??``).trim());return t}function ge(e){return e.replace(/[^a-z0-9一-鿿]+/gi,``).toLowerCase()}function _e(e,t){if(!t||!e)return e;let n=me(t),r=new Set([...he(t)].map(e=>ge(e)));if(n.size===0&&r.size===0)return e;let i=e.replace(/<!-- otto-memory:start slug="([^"]+)" -->[\s\S]*?<!-- otto-memory:end slug="\1" -->\n?/g,(e,t)=>n.has(t)?``:e).split(`
|
|
169
|
+
`),a=[],o=!1;for(let e of i){let t=e.match(/^\s*-\s+\*\*([^*]+)\*\*/);if(t&&r.has(ge(t[1]??``))){o=!0;continue}if(o){(e.trim()===``||/^\s*- /.test(e)||e.startsWith(`## `))&&(o=!1,a.push(e));continue}a.push(e)}return a.join(`
|
|
170
|
+
`)}var ve=class{memories=new Map;unwatch;constructor(e,t=ue,n){this.store=e,this.sources=t,this.entryMtimes=n}async load(){this.memories=await this.store.load(this.sources);let e=this.memories.get(`./.otto/AGENTS.md`),t=this.sources.find(e=>e.path===`./AGENTS.md`)?.path;if(t&&e&&this.memories.has(t)){let n=this.memories.get(t)??``,r=_e(n,e);r!==n&&this.memories.set(t,r)}if(t&&this.memories.has(t)){let e=this.memories.get(t)??``,n=fe(e);n!==e&&this.memories.set(t,n)}D.info(`Loaded ${this.memories.size} memory source(s)`)}async reload(){await this.load()}getInjection(){return ae(this.memories,T,this.entryMtimes)}getMemories(){return this.memories}async write(e,t){let n=this.sources.find(t=>t.path===e);if(!n)throw Error(`Unknown memory source: ${e}`);if(!n.writable)throw Error(`Memory source is read-only: ${e}`);await this.store.write(e,t),this.memories.set(e,t),D.info(`Memory source written: ${e}`)}watching(){this.unwatch||!this.store.watch||(this.unwatch=this.store.watch(this.sources,e=>{D.info(`Memory source changed: ${e}, reloading...`),this.reload()}))}stop(){this.unwatch?.(),this.unwatch=void 0}dispose(){this.stop(),this.memories.clear()}},ye=class{data=new Map;set(e,t){this.data.set(e,t)}async load(e){let t=new Map;for(let n of e){let e=this.data.get(n.path);e!==void 0&&t.set(n.path,e)}return t}async write(e,t){this.data.set(e,t)}},O=class{cwd;homeDirOverride;constructor(e=process.cwd(),t){this.cwd=e,this.homeDirOverride=t}async load(e){let{readFile:t}=await import(`node:fs/promises`),{resolve:n,isAbsolute:r}=await import(`node:path`),{homedir:i}=await import(`node:os`),a=new Map;for(let o of e){let e=this.resolvePath(o.path,n,r,i);try{let n=await t(e,`utf-8`);a.set(o.path,n)}catch{}}return a}async write(e,t){let{mkdir:n}=await import(`node:fs/promises`),{resolve:r,dirname:i,isAbsolute:a}=await import(`node:path`),{homedir:o}=await import(`node:os`),{atomicWriteFile:s}=await import(`@x-otto/persistence`),c=this.resolvePath(e,r,a,o);await n(i(c),{recursive:!0}),await s(c,t)}resolvePath(e,t,n,r){let i=this.homeDirOverride??r(),a=e.startsWith(`~/`)?e.replace(`~/`,`${i}/`):e;return n(a)?a:t(this.cwd,a)}};const be=e(`@x-otto/memory:token-estimator`);let k=null,A=null;async function xe(){if(!k){if(A){await A;return}A=(async()=>{try{let{getEncoding:e}=await import(`js-tiktoken`),t=e(`cl100k_base`);k={encode:e=>typeof e!=`string`||e.length===0?[]:t.encode(e)},be.info(`Tokenizer loaded: cl100k_base`)}catch(e){be.warn(`Failed to load js-tiktoken, falling back to heuristic: %s`,e.message),k=null}finally{A=null}})(),await A}}const Se=2e3;function Ce(e){return e>=33&&e<=47||e>=58&&e<=64||e>=91&&e<=96||e>=123&&e<=126}function we(e){return e>=12352&&e<=12543||e>=13312&&e<=19903||e>=19968&&e<=40959||e>=63744&&e<=64255||e>=44032&&e<=55203||e>=65280&&e<=65519||e>=131072&&e<=191471}function j(e){let t=0,n=0,r=0;for(let i of e){let e=i.codePointAt(0)??0;we(e)?t+=1:Ce(e)?n+=1:r+=1}return Math.ceil(t*1+n*.5+r*.25)}function Te(e,t){let n=1;for(let r=1;r<e.length;r++)if(e.charCodeAt(r)===e.charCodeAt(r-1)){if(n++,n>=t)return!0}else n=1;return!1}function M(e){if(k){if(e.length>1e5||Te(e,64))return j(e);try{return k.encode(e).length}catch{return j(e)}}return A||xe().catch(()=>{}),j(e)}function Ee(e){return e?e.length<=6?e:e.slice(-6):`?`}function N(e){return typeof e.content==`string`?e.content:Array.isArray(e.content)?e.content.map(e=>{switch(e.type){case`text`:return e.text;case`thinking`:return e.text;case`tool_call`:return`[call#${Ee(e.id)} ${e.name}](${JSON.stringify(e.arguments)})`;case`image`:return`[image]`;default:return``}}).filter(Boolean).join(`
|
|
171
|
+
`):JSON.stringify(e)}const De=new WeakMap;function P(e,t=M){let n=t===M&&k!==null&&typeof e==`object`&&!!e;if(n){let t=De.get(e);if(t!==void 0)return t}let r;if(Array.isArray(e.content)){r=4;for(let n of e.content)switch(n.type){case`text`:case`thinking`:r+=t(n.text);break;case`tool_call`:r+=t(`${n.name}(${JSON.stringify(n.arguments)})`);break;case`image`:r+=Se;break;default:break}}else r=4+t(N(e));return n&&De.set(e,r),r}function F(e,t=M){let n=0;for(let r of e)n+=P(r,t);return n}function Oe(e){return e.role===`assistant`}function I(e,t=M){let n=-1,r=0;for(let t=e.length-1;t>=0;t--){let i=e[t];if(Oe(i)&&i.usage){n=t,r=(i.usage.cacheReadTokens??0)+(i.usage.cacheWriteTokens??0)+i.usage.inputTokens+i.usage.outputTokens;break}}let i=0,a=0;if(n>=0){for(let r=0;r<=n;r++)a+=P(e[r],t);for(let r=n+1;r<e.length;r++)i+=P(e[r],t)}else i=F(e,t);let o=r+i,s=n>=0?Math.max(0,r-a):0,c=Math.max(0,o-s);return{total:o,fromUsage:r,fromEstimate:i,lastUsageIndex:n,derivedPrefix:s,bodyAfterPrefix:c}}const ke=.1,L=16384,Ae=t(process.env.OTTO_MEMORY_COMPACTION_HARD_CEILING_FRACTION,.9),je=24e3,Me=.6,Ne=.15,Pe=2e4,Fe=2e3,Ie=`write`,Le=`edit`,Re=`read`,ze=`grep`,Be=`find`,Ve=`read_file`,He=`grep_search`,Ue=`file_search`,We=t(process.env.OTTO_MEMORY_COVERAGE_WARN_THRESHOLD,.7),Ge=t(process.env.OTTO_MEMORY_COVERAGE_ERROR_THRESHOLD,.5),Ke=t(process.env.OTTO_MEMORY_COVERAGE_FLOOR_THRESHOLD,.3),qe=[Ie,Le],R=[Re,ze,Be,Ve,He,Ue,`bash`,`bash_output`,`web_search`,`web_fetch`];function z(e){return{compaction:{enabled:!0,trigger:[`headroom`,Math.min(L,e.maxOutput)+je],keepRecent:[`fraction`,ke],reserveTokens:Math.min(L,e.maxOutput)},prune:{trigger:[`fraction`,Me],protect:[`fraction`,Ne],minimum:Pe,regenerableTools:[...R],protectedTools:[],truncateTools:[...qe],truncateMaxLength:Fe}}}const B={enabled:!0,trigger:[`headroom`,L+je],keepRecent:[`fraction`,ke],reserveTokens:L},V={trigger:[`fraction`,Me],protect:[`fraction`,Ne],minimum:Pe,regenerableTools:[...R],protectedTools:[],truncateTools:[...qe],truncateMaxLength:Fe,timeBased:{enabled:!1,gapThresholdMs:60*6e4,keepRecent:5}};function H(e,t){let[n,r]=e;switch(n){case`tokens`:return r;case`fraction`:return Math.floor(t*r);case`headroom`:{let e=Math.floor(t*.5);return Math.max(e,t-r)}}}const Je=e(`@x-otto/memory:prune`);function Ye(e,t,n={},r=M,i={}){let a={...V,...n},o=H(a.trigger,t),s=F(e,r),c=i.currentTokens??s,l=a.timeBased,u=l?.enabled===!0&&i.timeGapMs!==void 0&&i.timeGapMs>(l.gapThresholdMs??36e5);if(!i.force&&!u&&c<o)return{messages:[...e],prunedCount:0,tokensSaved:0,changed:!1};let d=u?Xe(e,a,r,l.keepRecent??5):Ze(e,a,t,r),f=s-F(d,r);if(f<a.minimum)return Je.debug(`Prune skipped: savings ${f} < minimum ${a.minimum}`),{messages:[...e],prunedCount:0,tokensSaved:0,changed:!1};let p=d.filter((t,n)=>t!==e[n]).length;return Je.info(`Pruned ${p} messages, saved ~${f} tokens`),{messages:d,prunedCount:p,tokensSaved:f,changed:!0}}function Xe(e,t,n,r){let i=new Set(t.regenerableTools),a=new Set(t.protectedTools),o=e=>{if(e.role!==`tool_result`)return!1;let t=e;return i.has(t.toolName)&&!a.has(t.toolName)},s=0,c=0;for(let t=e.length-1;t>=0;t--){let n=e[t];if(n&&o(n)&&(s++,s===r)){c=t;break}}return e.map((e,t)=>t>=c||!o(e)?e:Qe(e,n))}function Ze(e,t,n,r){let i=H(t.protect,n),a=new Set(t.truncateTools),o=new Set(t.regenerableTools),s=new Set(t.protectedTools),c=0,l=e.length;for(let t=e.length-1;t>=0;t--){let n=e[t],a=P(n,r);if(c+=a,c>=i){l=t+1;break}}return e.map((e,n)=>{if(n>=l)return e;if(e.role===`tool_result`){let t=e;return!o.has(t.toolName)||s.has(t.toolName)?e:Qe(t,r)}return e.role===`assistant`&&n<l?et(e,a,t.truncateMaxLength):e})}function Qe(e,t){let n=e.content.map(e=>e.type===`text`?e.text:``).join(``),r=t(n);if(r<100)return e;let i=n.split(`
|
|
172
|
+
`).find(e=>e.startsWith(`[tool result stored to `)),a=i?` ${i}`:``;return{...e,content:[{type:`text`,text:`[output pruned — ${r} tokens]${a}`}]}}function $e(e,t){let n=typeof t.path==`string`?t.path:void 0;if(n===void 0)return null;if(e===`write`)return{path:n,content:`[content omitted by context pruning — ${(typeof t.content==`string`?t.content:``).length} chars. The file was already written to disk; read it if needed.]`};if(e===`edit`){let e=typeof t.oldContent==`string`?t.oldContent:``,r=typeof t.newContent==`string`?t.newContent:``;return{path:n,oldContent:`[omitted by context pruning — ${e.length} chars]`,newContent:`[omitted by context pruning — ${r.length} chars. The edit was already applied to the file on disk; read it if needed.]`}}return null}function et(e,t,n){if(!Array.isArray(e.content))return e;let r=!1,i=e.content.map(e=>{if(e.type!==`tool_call`||!t.has(e.name)||JSON.stringify(e.arguments).length<=n)return e;let i=$e(e.name,e.arguments);return i===null?e:(r=!0,{...e,arguments:i})});return r?{...e,content:i}:e}function U(e){return`${e.role===`user`?`Human`:e.role===`assistant`?`Assistant`:e.role===`tool_result`?`Tool[result#${Ee(e.toolCallId)} ${e.toolName}]${e.isError?`{error}`:``}`:`System`}: ${N(e)}`}function W(e,t){if(e.length<=t)return e;let n=e=>`\n\n…[${e} chars omitted to fit the summarization window]…\n\n`,r=Math.max(0,t-n(e.length).length),i=Math.floor(r/2);if(i<=0)return n(e.length).slice(0,t);let a=e.length-i*2;return`${e.slice(0,i)}${n(a)}${e.slice(-i)}`}function tt(e,t,n){if(n(e)<=t)return e;let r=0,i=e.length,a=``;for(;r<=i;){let o=Math.floor((r+i)/2),s=W(e,o);n(s)<=t?(a=s,r=o+1):i=o-1}return a}function G(e){return e.role===`user`?0:e.role===`assistant`?1:e.role===`tool_result`?nt.has(e.toolName)?3:2:1}const nt=new Set(R),rt=4096;async function it(e,t){if(!t)return[...e];let n=!1,r=[];for(let i of e){if(i.role!==`tool_result`||G(i)!==3){r.push(i);continue}let e=i,a=N(i);if(a.length<4096){r.push(i);continue}let o=await t(e.toolCallId??``,a).catch(()=>void 0);o?(n=!0,r.push({...e,content:[{type:`text`,text:`[tool result stored to ${o} (${a.length} chars). Use the read tool on that path to recover the full output.]`}]})):r.push(i)}return n?r:[...e]}function at(e,t){let n=e.map(e=>({msg:e,prio:G(e),text:U(e)})),r=n.reduce((e,t)=>e+t.text.length,0);if(r<=t)return{texts:n.map(e=>e.text),sourceChars:r,fedChars:r,truncated:!1};let i=!1,a=e=>n.filter(t=>t.prio===e),o=a(0),s=a(1),c=a(2),l=a(3),u=e=>e.reduce((e,t)=>e+t.text.length,0),d=[{msgs:l,share:.1},{msgs:c,share:.2},{msgs:s,share:.35}],f=t;for(let{msgs:e,share:n}of d){if(e.length===0)continue;let r=u(e),a=Math.floor(t*n),o=Math.min(r,a);st(e,o),o<r&&(i=!0),f-=u(e)}o.length>0&&u(o)>f&&(st(o,Math.max(0,f)),i=!0);let p=n.reduce((e,t)=>e+t.text.length,0);return{texts:n.map(e=>e.text),sourceChars:r,fedChars:p,truncated:i}}function ot(e,t){if(e.length===0||t<=0)return{text:``,chars:0,truncated:!1};let n=at(e,t),r=n.texts.filter(e=>e.length>0).join(`
|
|
179
173
|
|
|
180
|
-
`);return{text:r,chars:r.length,truncated:n.truncated}}function
|
|
174
|
+
`);return{text:r,chars:r.length,truncated:n.truncated}}function st(e,t){if(e.length===0)return;let n=Math.max(0,Math.floor(t/e.length));for(let t of e)t.text.length>n&&(t.text=n<=0?``:W(t.text,n))}const K=new Map;function ct(e){return K.get(e)??0}function lt(e,t){K.set(e,t)}function ut(e){let t=K.get(e)??0;return t>0&&K.delete(e),t}function dt(e){K.delete(e)}const q=e(`@x-otto/memory:compaction`);function ft(e){return e.role===`user`&&N(e).startsWith(n)}function pt(e){return e.startsWith(n)?e.slice(n.length).trimStart():e}const mt=new Set([Re,ze,Be,Ve,He,Ue]),ht=new Set([Ie,Le,`write_file`,`edit_file`,`replace_string_in_file`]);function gt(e,t,n=M){let r=0,i=0;for(let a=e.length-1;a>=0;a--){let o=P(e[a],n);if(r+=o,r>=t){i=a+1;break}}i=Math.max(0,Math.min(i,e.length-1));let a=i;for(;a<e.length;){let t=e[a];if(t.role===`user`||t.role===`assistant`)break;a++}let o=!1,s=a;if(a>0&&a<e.length&&e[a].role===`assistant`){let t=a-1;for(;t>=0&&e[t].role===`tool_result`;)t--;t>=0&&e[t].role===`assistant`&&(o=!0,s=t)}return{firstKeptIndex:a,turnStartIndex:s,isSplitTurn:o}}function _t(e,t,n={},r=M){let i=H({...B,...n}.trigger,t);return F(e,r)>=i*.5}function vt(e,t,n={},r,i=M,a){let o=H({...B,...n}.keepRecent,t),s=F(e,i);if(e.length<=2)return q.debug(`Too few messages to compact`),null;let c=gt(e,a?.force===!0?Math.max(1,Math.min(o,Math.floor(s/2))):o,i);if(c.firstKeptIndex<=0)return q.debug(`Cut point at beginning, nothing to compact`),null;let l=e.slice(0,c.firstKeptIndex),u=e.slice(c.firstKeptIndex),d=l.filter(e=>!ft(e));if(d.length===0)return q.debug(`Compaction window only contains prior summaries, nothing new to summarize`),null;let f=r;if(!f){for(let e=l.length-1;e>=0;e--)if(ft(l[e])){f=pt(N(l[e]));break}}let p=[];c.isSplitTurn&&(p=e.slice(c.turnStartIndex,c.firstKeptIndex));let m=xt(d);return{messagesToSummarize:[...d],turnPrefixMessages:p,preservedMessages:[...u],isSplitTurn:c.isSplitTurn,tokensBefore:s,previousSummary:f,fileOps:m}}function yt(e,t,n){let r=[],i=[],a=0;for(let o of e){let e=P(o,n);i.length>0&&a+e>t&&(r.push(i),i=[],a=0),i.push(o),a+=e}return i.length>0&&r.push(i),r}async function bt(e,t,n={},r,i=M,a,o){let s={...B,...n};q.info(`Compacting ${e.messagesToSummarize.length} messages`);let c=s.contextWindow==null?0:i(C(``,void 0,s.customInstructions,{variant:`segment`})),l=s.contextWindow==null?void 0:Math.max(1,Math.floor(s.contextWindow*.6)-c),u=l==null?0:Math.floor(l*.2),d=l==null?void 0:l-u,f=l==null?{text:``,chars:0,truncated:!1}:ot(e.preservedMessages??[],u*4),p,m=1,h=o?await it(e.messagesToSummarize,o):e.messagesToSummarize,g=d==null?[h]:yt(h,d,i);if(g.length<=1){let n=h.map(U).join(`
|
|
181
175
|
|
|
182
|
-
`),i=e.preservedMessages?.length??0
|
|
176
|
+
`),i=e.preservedMessages?.length??0,o={variant:i>0?`preserved-tail`:`full`,preservedCount:i,...f.text?{preservedTailReference:f.text}:{}};p=s.prefixReuse?await t(C(n,e.previousSummary,s.customInstructions,o),{maxTokens:s.reserveTokens,signal:r,prefix:{sessionId:a,messages:h}}):await t(C(n,e.previousSummary,s.customInstructions,o),{maxTokens:s.reserveTokens,signal:r}),q.info({batches:1,sourceChars:n.length,fedChars:n.length,coverage:1,truncated:!1,preservedTailChars:f.chars,preservedTailTruncated:f.truncated},`[compaction] summarization coverage`)}else{let n=a??`__default__`,o=ct(n),c=o>0?o:8;o>0&&ut(n);let l=g.length<=c?g:[...g.slice(0,c-1),g.slice(c-1).flat()];q.info(`RFC-321 M6: summarization input exceeds window budget; map-reduce over ${l.length} batches`);let u=d*4,h=[],_=0,v=0,y=!1,b=[0,0,0,0],x=[0,0,0,0];for(let e of l){if(r?.aborted)break;let n=at(e,u),o=n.texts.join(`
|
|
183
177
|
|
|
184
|
-
`),
|
|
178
|
+
`),c=o;c.length>u&&(c=W(c,u)),c=tt(c,d,i),_+=n.sourceChars,v+=Math.min(n.fedChars,c.length),(n.truncated||c.length<o.length)&&(y=!0),e.forEach((e,t)=>{let r=G(e);b[r]=(b[r]??0)+U(e).length,x[r]=(x[r]??0)+(n.texts[t]?.length??0)});let l=C(c,void 0,s.customInstructions,{variant:`segment`,...f.text?{preservedTailReference:f.text}:{}});h.push(await t(l,{maxTokens:s.reserveTokens,signal:r,...s.prefixReuse?{prefix:{sessionId:a}}:{}}))}p=await t(te(W(h.map((e,t)=>`### Segment ${t+1}\n\n${e}`).join(`
|
|
185
179
|
|
|
186
|
-
`),
|
|
180
|
+
`),u),e.previousSummary,s.customInstructions),{maxTokens:s.reserveTokens,signal:r,...s.prefixReuse?{prefix:{sessionId:a}}:{}});let S=_>0?v/_:1;m=S,q.info({batches:l.length,sourceChars:_,fedChars:v,coverage:Number(S.toFixed(4)),truncated:y,preservedTailChars:f.chars,preservedTailTruncated:f.truncated,byPriority:{p0:{src:b[0],fed:x[0]},p1:{src:b[1],fed:x[1]},p2:{src:b[2],fed:x[2]},p3:{src:b[3],fed:x[3]}}},`[compaction] summarization coverage`);let ee=s.coverageWarnThreshold??We,w=Math.min(s.coverageErrorThreshold??Ge,ee);S<w&&q.warn({coverage:Number(S.toFixed(4)),sourceChars:_,fedChars:v,batches:l.length,errorThreshold:w},`[compaction] low summarization coverage — large portion of history not represented in summary (RFC-321 C1)`),Ct(S,l.length,a??`__default__`,w,ee)}if(e.fileOps.read.length>0||e.fileOps.modified.length>0){let t=St(e.fileOps);p.includes(`## File Operations`)||(p+=`\n\n${t}`)}if(e.isSplitTurn&&e.turnPrefixMessages.length>0){let n=await t(w(e.turnPrefixMessages.map(U).join(`
|
|
187
181
|
|
|
188
|
-
`)),{signal:r});
|
|
189
|
-
`)}function
|
|
190
|
-
`)}function pt(e){if(e.role===`assistant`&&e.usage){let{usage:t,...n}=e;return n}return e}const J=e(`@x-otto/memory`);function Y(e){return e?.trim()||`__default__`}var mt=class{persistent;archiveStorage;loadSessionEntries;messageFromEntry;summarize;estimateTokens;rawConfig;contextWindow;getContextWindow;compactionConfig;pruneConfig;maxContentBytes;getMaxContentBytes;getEstimatedBytes;previousSummaries=new Map;archiveIdCounter=new Map;compactionInFlight=new Set;constructor(e){let t=e.model?z(e.model):{compaction:B,prune:V};this.compactionConfig={...t.compaction,...e.compaction},this.pruneConfig={...t.prune,...e.prune},this.contextWindow=e.model?.contextWindow??2e5,this.getContextWindow=e.getContextWindow,this.maxContentBytes=e.maxContentBytes,this.getMaxContentBytes=e.getMaxContentBytes,this.getEstimatedBytes=e.getEstimatedBytes,this.rawConfig=e,this.archiveStorage=e.archiveStorage??new ft,this.loadSessionEntries=e.loadSessionEntries,this.messageFromEntry=e.messageFromEntry,this.summarize=e.summarize,this.estimateTokens=e.estimateTokens??M,this.persistent=new ce(e.memoryStore??new O,e.sources)}clearSession(e){this.previousSummaries.delete(Y(e))}forgetSession(e){this.previousSummaries.delete(Y(e)),this.archiveIdCounter.delete(e),Qe(e),this.archiveStorage.forgetSession?.(e)}reconfigure(e){let t=z(e);this.contextWindow=e.contextWindow,this.compactionConfig={...t.compaction,...this.rawConfig.compaction},this.pruneConfig={...t.prune,...this.rawConfig.prune}}effectiveContextWindow(){return this.getContextWindow?.()??this.contextWindow}async ensureFitsWindow(e,t,n){if(!this.needsCompaction(e))return{messages:[...e],compacted:!1};let r=await this.process(e,t,n,{force:!0});return{messages:r.messages,compacted:r.compacted,summary:r.summary,replacement:r.replacement}}async loadMemory(){await this.persistent.load()}getMemoryPrompt(){return this.persistent.getInjection()}async reloadMemory(){await this.persistent.reload()}getMemories(){return this.persistent.getMemories()}needsPrune(e){return this.needsPruneWith(I(e,this.estimateTokens))}needsPruneWith(e){let t=H(this.pruneConfig.trigger,this.effectiveContextWindow());return e.bodyAfterPrefix>=t}prune(e,t=!1){return this.pruneWith(e,I(e,this.estimateTokens),t)}pruneWith(e,t,n){return ze(e,this.effectiveContextWindow(),this.pruneConfig,this.estimateTokens,{currentTokens:t.total,force:n})}needsCompaction(e){if(this.compactionConfig.enabled===!1)return!1;let t=this.effectiveContextWindow(),n=I(e,this.estimateTokens),r=H(this.compactionConfig.trigger,t),i=this.compactionConfig.hardCeilingFraction??be,a=Math.max(r,Math.floor(t*i));return!!(n.bodyAfterPrefix>=r||n.total>=a||this.residencyPressure())}residencyPressure(){if(this.getEstimatedBytes==null)return!1;let e=this.getMaxContentBytes?.()??this.maxContentBytes;if(e==null)return!1;let t=this.getEstimatedBytes();return t!=null&&t>=e*.9}isEligibleForManualCompact(e){return it(e,this.effectiveContextWindow(),this.compactionConfig,this.estimateTokens)}prepareCompaction(e,t,n){return at(e,this.effectiveContextWindow(),this.compactionConfig,this.previousSummaries.get(Y(t)),this.estimateTokens,n)}async compact(e,t,n){let r=await st(e,this.summarize,{...this.compactionConfig,contextWindow:this.effectiveContextWindow(),...(()=>{let e=this.rawConfig.getCoverageThresholds?.();return{...e?.warn===void 0?{}:{coverageWarnThreshold:e.warn},...e?.error===void 0?{}:{coverageErrorThreshold:e.error},...e?.floor===void 0?{}:{coverageFloorThreshold:e.floor}}})()},n,this.estimateTokens,t),i=Math.min(this.compactionConfig.coverageFloorThreshold??this.rawConfig.getCoverageThresholds?.()?.floor??Ie,this.compactionConfig.coverageErrorThreshold??Fe);if(r.coverage<i&&(J.warn({sessionId:t,coverage:Number(r.coverage.toFixed(4)),floorThreshold:i},`[compaction] coverage below floor — boundary established WITH an explicit low-fidelity notice; a large portion of history is not represented in this summary (RFC-340 D1)`),r.summary=`${oe(r.coverage)}\n\n${r.summary}`),t)try{await this.recordCompactionArchive(t,r,e.messagesToSummarize),J.info(`Compaction recorded at ${r.archivePath}`)}catch(e){J.warn({error:e},`Failed to record compaction archive`)}return this.previousSummaries.set(Y(t),r.summary),r}async recordCompactionArchive(e,t,n){let r=(this.archiveIdCounter.get(e)??0)+1;this.archiveIdCounter.set(e,r),t.archivePath=`archive:${e}:${r}`,this.loadSessionEntries||await this.archiveStorage.archive(e,n,t.summary)}async process(e,t,r,i){let a=[...e],o=!1,s=!1,c,l,u,d,f=i?.force===!0,p=i?.skipPrune===!0,m=I(a,this.estimateTokens);if(!p&&(f||this.needsPruneWith(m))){let e=a.length,t=this.pruneWith(a,m,f);t.changed&&(a=t.messages,o=!0,d={prunedCount:t.prunedCount,tokensSaved:t.tokensSaved,messagesBefore:e,messagesAfter:a.length},J.info(`Prune: removed ${t.prunedCount} tool outputs, saved ~${t.tokensSaved} tokens`))}let h=this.compactionConfig.enabled!==!1;if(f?h:this.needsCompaction(a)){let e=Y(t);if(this.compactionInFlight.has(e))J.info({sessionId:t},`Compaction already in flight for this session, skipping this round (will retry next round)`);else{this.compactionInFlight.add(e);try{let e=this.prepareCompaction(a,t,{force:f||this.residencyPressure()});if(e)try{let i=await this.compact(e,t,r);s=!0,c=i.summary,u=i.archivePath;let o={role:`user`,content:[{type:`text`,text:`${n}\n\n${u?ae(u,i.summary):i.summary}`}],timestamp:Date.now()};l=e.preservedMessages.map(pt),a=[o,...l],J.info(`Compaction: ${e.messagesToSummarize.length} messages → summary, kept ${e.preservedMessages.length}`)}catch(e){J.warn({error:e},`Compaction failed (LLM/summarizer error?), skipping compaction for this turn`)}}finally{this.compactionInFlight.delete(e)}}}return{messages:a,summary:c,replacement:l,archivePath:u,pruned:o,compacted:s,pruneMetrics:d}}async listArchives(e){if(this.loadSessionEntries&&this.messageFromEntry)try{return await this.listArchivesFromEntries(e)}catch(e){J.warn({error:e},`listArchives from session entries failed, falling back`)}return this.archiveStorage.list(e)}async listArchivesFromEntries(e){let t=await this.loadSessionEntries(e),n=this.messageFromEntry,r=[],i=0;for(let a=0;a<t.length;a++){let o=t[a];if(o.type!==`compaction`)continue;let s=o.data;i++;let c=0,l=0;for(let e=a-1;e>=0;e--){let r=t[e];if(r.type===`compaction`)break;let i=n(r);i&&(c++,l=i.timestamp??l)}r.push({path:`archive:${e}:${i}`,timestamp:l||o.seq||0,messageCount:s.compactedCount,summary:s.summary.slice(0,200)})}return r}async readArchive(e){if(this.loadSessionEntries&&this.messageFromEntry)try{let t=await this.readArchiveFromEntries(e);if(t)return t}catch(e){J.warn({error:e},`readArchive from session entries failed, falling back`)}return this.archiveStorage.read(e)}async readArchiveFromEntries(e){let t=e.match(/^archive:(.+):(\d+)$/);if(!t)return null;let n=t[1],r=parseInt(t[2],10),i=await this.loadSessionEntries(n),a=this.messageFromEntry,o=0,s=-1;for(let e=0;e<i.length;e++)if(i[e].type===`compaction`&&(o++,o===r)){s=e;break}if(s<0)return null;let c=i[s].data,l=-1;for(let e=s-1;e>=0;e--)if(i[e].type===`compaction`){l=e;break}let u=l+1,d=[];for(let e=u;e<s;e++){let t=a(i[e]);t&&d.push(t)}let f=Date.now();return q(d.filter(e=>e!==null),c.summary,f)}dispose(){this.persistent.dispose()}};function ht(e){return new mt(e)}var gt=class{baseUrl;userId;workspaceKeyGetter;isWorkspaceSyncEnabled;getAuth;fetchImpl;timeoutMs;local;onConflict;conflictCounters=new Map;knownVersions=new Map;constructor(e){this.baseUrl=e.baseUrl.replace(/\/+$/,``),this.userId=e.userId,this.workspaceKeyGetter=e.workspaceKey,this.isWorkspaceSyncEnabled=e.isWorkspaceSyncEnabled??(()=>!1),this.getAuth=e.getAuth,this.fetchImpl=e.fetch,this.timeoutMs=e.timeoutMs,this.local=new O(e.localDir,e.homeDirOverride),this.onConflict=e.onConflict}async load(e){let t=new Map,n=[];for(let r of e)if(this.userId&&_t(r.path)){let e=await this.loadRemoteUser(r.path);e===null?n.push(r):(t.set(r.path,e),this.local.write(r.path,e).catch(()=>{}))}else if(this.isWorkspaceSourceEnabled(r.path)){let e=await this.loadRemoteWorkspace(r.path);e===null?n.push(r):(t.set(r.path,e),this.local.write(r.path,e).catch(()=>{}))}else n.push(r);if(n.length>0){let e=await this.local.load(n);for(let[n,r]of e)t.set(n,r)}return t}async write(e,t){await this.local.write(e,t),this.userId&&_t(e)?this.writeRemoteUser(e,t).catch(()=>{}):this.isWorkspaceSourceEnabled(e)&&await this.writeRemoteWorkspace(e,t)}watch(e,t){return()=>{}}isWorkspaceSourceEnabled(e){return vt(e)&&this.isWorkspaceSyncEnabled()&&!!this.workspaceKeyGetter?.()}remoteKey(e){let t=e.startsWith(`~/.otto/`)?e.slice(8):e;return`user:${this.userId}:${t}`}remoteResourceId(e){let t=e.startsWith(`~/.otto/`)?e.slice(8):e;return encodeURIComponent(t)}workspaceResourceId(e){let t=e.startsWith(`./.otto/`)?e.slice(8):e;return encodeURIComponent(t)}async loadRemoteUser(e){try{let t={Authorization:`Bearer ${(await this.getAuth()).token}`,Accept:`application/json`,"X-Otto-User-Id":this.userId},n=new AbortController,r=setTimeout(()=>n.abort(),this.timeoutMs);try{let r=await this.fetchImpl(`${this.baseUrl}/memory/user/${this.remoteResourceId(e)}`,{method:`GET`,headers:t,signal:n.signal});if(r.ok){let e=await r.json();if(typeof e.content==`string`)return e.content}if(r.status===404){let r=await this.fetchImpl(`${this.baseUrl}/sessions/${encodeURIComponent(this.remoteKey(e))}`,{method:`GET`,headers:t,signal:n.signal});if(r.ok){let e=await r.json();if(typeof e.content==`string`)return e.content}}}finally{clearTimeout(r)}}catch{}return null}async writeRemoteUser(e,t){try{let n={Authorization:`Bearer ${(await this.getAuth()).token}`,"Content-Type":`application/json`,"X-Otto-User-Id":this.userId},r=new AbortController,i=setTimeout(()=>r.abort(),this.timeoutMs);try{await this.fetchImpl(`${this.baseUrl}/memory/user/${this.remoteResourceId(e)}`,{method:`PUT`,headers:n,body:JSON.stringify({content:t}),signal:r.signal})}finally{clearTimeout(i)}}catch{}}async loadRemoteWorkspace(e){let t=this.workspaceKeyGetter?.();if(!t)return null;try{let n={Authorization:`Bearer ${(await this.getAuth()).token}`,Accept:`application/json`},r=new AbortController,i=setTimeout(()=>r.abort(),this.timeoutMs);try{let i=await this.fetchImpl(`${this.baseUrl}/memory/workspace/${encodeURIComponent(t)}/${this.workspaceResourceId(e)}`,{method:`GET`,headers:n,signal:r.signal});if(i.ok){let t=await i.json();if(typeof t.content==`string`)return typeof t.version==`number`&&this.knownVersions.set(e,t.version),t.content}}finally{clearTimeout(i)}}catch{}return null}async writeRemoteWorkspace(e,t){let n=this.workspaceKeyGetter?.();if(n)try{let r={Authorization:`Bearer ${(await this.getAuth()).token}`,"Content-Type":`application/json`},i=new AbortController,a=setTimeout(()=>i.abort(),this.timeoutMs),o=this.knownVersions.get(e)??0,s=o+1;try{let a=await this.fetchImpl(`${this.baseUrl}/memory/workspace/${encodeURIComponent(n)}/${this.workspaceResourceId(e)}`,{method:`PUT`,headers:r,body:JSON.stringify({content:t,version:s,expectedVersion:o}),signal:i.signal});if(a.status===409){await this.writeConflictSidecar(e,t);return}a.ok&&this.knownVersions.set(e,s)}finally{clearTimeout(a)}}catch{}}async writeConflictSidecar(e,t){let n=this.conflictCounters.get(e)??0,r=n%5;this.conflictCounters.set(e,n+1);let i=`${e}.conflict-${r}`;try{await this.local.write(i,t),this.onConflict?.(e,i)}catch{}}};function _t(e){return e===`~/.otto/AGENTS.md`||e===`~/.otto/MEMORY.md`||e.startsWith(`~/.otto/memory/`)}function vt(e){return e===`./.otto/MEMORY.md`||e.startsWith(`./.otto/memory/`)&&!e.startsWith(`./.otto/local/`)||e===`./.otto/lessons.json`}function yt(e){let t=new Map;return{async load(n){let r=n.filter(e=>!t.has(e.path)),i=r.length>0?await e.load(r):new Map;for(let e of n){let n=t.get(e.path);n!==void 0&&i.set(e.path,n)}return i},async write(e,n){t.set(e,n)}}}function bt(e,t={}){let n=t.fileName??`AGENTS.md`,a=t.rootMarkers??[`.git`],s=t.maxDepth??25,l=t.maxFiles??10,u=t.maxFileBytes??64*1024,d=[],f=e;for(let e=0;e<s;e++){let e=c(f,n);try{r(e)&&i(e).size<=u&&d.push(e)}catch{}let t=a.some(e=>r(c(f,e))),s=o(f);if(t||s===f)break;f=s}return d.reverse().slice(-l).map(e=>({path:e,writable:!1}))}const xt=e(`@x-otto/memory:auto`),X=`# MEMORY`,St=/^- \[(.+?)\]\(memory\/(.+?)\.md\)/,Z=`./.otto/AGENTS.md`;function Ct(e,t,n){return`<!-- otto-memory:start slug="${e}" -->\n## ${t.replace(/[\r\n]+/g,` `).trim()}\n\n${n.trim()}\n<!-- otto-memory:end slug="${e}" -->`}function wt(e){return RegExp(`<!-- otto-memory:start slug="${e}" -->[\\s\\S]*?<!-- otto-memory:end slug="${e}" -->`)}var Tt=class{store;baseDir;localBaseDir;index=``;localIndex=``;loaded=!1;localLoaded=!1;constructor(e){this.store=e.store,this.baseDir=(e.baseDir??`./.otto`).replace(/\/+$/,``),this.localBaseDir=(e.localBaseDir??`${this.baseDir}/local`).replace(/\/+$/,``)}get indexPath(){return`${this.baseDir}/MEMORY.md`}entryPathOf(e){return`${this.baseDir}/memory/${e}.md`}get localIndexPath(){return`${this.localBaseDir}/MEMORY.md`}localEntryPathOf(e){return`${this.localBaseDir}/memory/${e}.md`}asSource(){return{path:this.indexPath,writable:!0}}asLocalSource(){return{path:this.localIndexPath,writable:!0}}async load(){this.index=(await this.store.load([this.asSource()])).get(this.indexPath)??``,this.loaded=!0}async loadLocal(){this.localIndex=(await this.store.load([this.asLocalSource()])).get(this.localIndexPath)??``,this.localLoaded=!0}getInjection(){let e=this.index.trim();return e?`${e}\n\n> Auto-memory entries live in the memory store (\`${this.baseDir}/memory/\`), not workspace files — read them with the memory_read tool; record new durable facts with memory_record.`:``}getLocalInjection(){let e=this.localIndex.trim();return e?`${e}\n\n> Machine-specific memory entries live in \`${this.localBaseDir}/memory/\` — never synced to other devices. Read with memory_read; record with memory_record (scope: 'local').`:``}async record(e,t=`workspace`){if(t===`local`){await this.recordLocal(e);return}if(t===`project-shared`){await this.recordProjectShared(e);return}await this.ensureLoaded();let n=this.resolveSlug(e.name,this.indexLines());await this.store.write(this.entryPathOf(n),`# ${e.name}\n\n${e.content}\n`);let r=`- [${e.name}](memory/${n}.md) — ${e.description}`,i=this.indexLines().filter(e=>!e.includes(`(memory/${n}.md)`));i.some(e=>e.startsWith(X))||i.unshift(X),i.push(r),this.index=`${i.join(`
|
|
191
|
-
`)}\n`,await this.store.write(this.indexPath,this.index),
|
|
192
|
-
`)}\n`,await this.store.write(this.localIndexPath,this.localIndex),
|
|
193
|
-
\n${i}\n`:`${t.trimEnd()}\n\n${i}\n`,await this.store.write(
|
|
182
|
+
`)),{signal:r,...s.prefixReuse?{prefix:{sessionId:a}}:{}});p=`${p}\n\n---\n\n${n}`}return q.info(`Compaction complete: summary ${p.length} chars`),{summary:p,firstKeptIndex:e.messagesToSummarize.length,tokensBefore:e.tokensBefore,coverage:m}}function xt(e){let t=new Set,n=new Set;for(let r of e){if(r.role!==`tool_result`)continue;let e=r.toolName,i=N(r).match(/(?:^|\s)(\/[^\s]+\.[a-z]+)/i);i?.[1]&&(mt.has(e)?t.add(i[1]):ht.has(e)&&n.add(i[1]))}return{read:[...t],modified:[...n]}}function St(e){let t=[`## File Operations`];if(e.read.length>0){t.push(`### Read`);for(let n of e.read)t.push(`- ${n}`)}if(e.modified.length>0){t.push(`### Modified`);for(let n of e.modified)t.push(`- ${n}`)}return t.join(`
|
|
183
|
+
`)}function Ct(e,t,n,r,i){e<r?lt(n,Math.max(t*2,16)):e<i&<(n,t+2)}const wt=e(`@x-otto/memory:archive`);var Tt=class{type=`memory`;archives=new Map;sessionIndex=new Map;async archive(e,t,n){let r=Date.now(),i=`memory://archives/${e}/compaction-${r}.md`,a=J(t,n,r);this.archives.set(i,{content:a,entry:{path:i,timestamp:r,summary:n.slice(0,200),messageCount:t.length}});let o=this.sessionIndex.get(e)??[];return o.push(i),this.sessionIndex.set(e,o),wt.info(`Archived ${t.length} messages for session ${e}`),i}async read(e){let t=this.archives.get(e);if(!t)throw Error(`Archive not found: ${e}`);return t.content}async list(e){return(this.sessionIndex.get(e)??[]).map(e=>this.archives.get(e)?.entry).filter(e=>e!==void 0)}forgetSession(e){let t=this.sessionIndex.get(e);if(t){for(let e of t)this.archives.delete(e);this.sessionIndex.delete(e)}}};function J(e,t,n){let r=[`## Compacted at ${new Date(n).toISOString()}`,``,`### Summary`,t,``,`### Original Messages (${e.length} messages)`];for(let t of e){let e=t.role===`user`?`Human`:t.role===`assistant`?`Assistant`:t.role===`tool_result`?`Tool[${t.toolName}]`:`System`,n=N(t),i=n.length>2e3?`${n.slice(0,2e3)}\n...(truncated, ${n.length} chars total)`:n;r.push(`\n**${e}**: ${i}`)}return r.join(`
|
|
184
|
+
`)}function Et(e){if(e.role===`assistant`&&e.usage){let{usage:t,...n}=e;return n}return e}const Y=e(`@x-otto/memory`),Dt=`__default__`;function X(e){return e?.trim()||Dt}var Ot=class{persistent;archiveStorage;loadSessionEntries;messageFromEntry;summarize;estimateTokens;rawConfig;contextWindow;getContextWindow;compactionConfig;pruneConfig;maxContentBytes;getMaxContentBytes;getEstimatedBytes;previousSummaries=new Map;archiveIdCounter=new Map;compactionInFlight=new Set;compactionLock;compactionSpill;clock;constructor(e){let t=e.model?z(e.model):{compaction:B,prune:V};this.compactionConfig={...t.compaction,...e.compaction},this.pruneConfig={...t.prune,...e.prune},this.contextWindow=e.model?.contextWindow??2e5,this.getContextWindow=e.getContextWindow,this.maxContentBytes=e.maxContentBytes,this.getMaxContentBytes=e.getMaxContentBytes,this.getEstimatedBytes=e.getEstimatedBytes,this.rawConfig=e,this.archiveStorage=e.archiveStorage??new Tt,this.loadSessionEntries=e.loadSessionEntries,this.messageFromEntry=e.messageFromEntry,this.summarize=e.summarize,this.estimateTokens=e.estimateTokens??M,this.compactionLock=e.compactionLock,this.compactionSpill=e.compactionSpill,this.clock=e.clock??{now:()=>Date.now()},this.persistent=new ve(e.memoryStore??new O,e.sources,e.entryMtimes)}clearSession(e){this.previousSummaries.delete(X(e))}forgetSession(e){this.previousSummaries.delete(X(e)),this.archiveIdCounter.delete(e),dt(e),this.archiveStorage.forgetSession?.(e)}reconfigure(e){let t=z(e);this.contextWindow=e.contextWindow,this.compactionConfig={...t.compaction,...this.rawConfig.compaction},this.pruneConfig={...t.prune,...this.rawConfig.prune}}effectiveContextWindow(){return this.getContextWindow?.()??this.contextWindow}async ensureFitsWindow(e,t,n){if(!this.needsCompaction(e))return{messages:[...e],compacted:!1};let r=await this.process(e,t,n,{force:!0});return{messages:r.messages,compacted:r.compacted,summary:r.summary,replacement:r.replacement}}async loadMemory(){await this.persistent.load()}getMemoryPrompt(){return this.persistent.getInjection()}async reloadMemory(){await this.persistent.reload()}getMemories(){return this.persistent.getMemories()}needsPrune(e){return this.needsPruneWith(I(e,this.estimateTokens),e)}needsPruneWith(e,t){let n=H(this.pruneConfig.trigger,this.effectiveContextWindow());return e.bodyAfterPrefix>=n?!0:t!==void 0&&this.timeGapTriggered(t)}timeGapTriggered(e){let t=this.pruneConfig.timeBased;if(t?.enabled!==!0)return!1;let n;for(let t=e.length-1;t>=0;t--){let r=e[t];if(r&&`timestamp`in r&&typeof r.timestamp==`number`){n=r.timestamp;break}}return n===void 0?!1:this.clock.now()-n>(t.gapThresholdMs??60*6e4)}prune(e,t=!1){return this.pruneWith(e,I(e,this.estimateTokens),t)}pruneWith(e,t,n){let r;if(this.pruneConfig.timeBased?.enabled===!0){let t;for(let n=e.length-1;n>=0;n--){let r=e[n];if(r&&`timestamp`in r&&typeof r.timestamp==`number`){t=r.timestamp;break}}t!==void 0&&(r=this.clock.now()-t)}return Ye(e,this.effectiveContextWindow(),this.pruneConfig,this.estimateTokens,{currentTokens:t.total,force:n,timeGapMs:r})}needsCompaction(e){if(this.compactionConfig.enabled===!1)return!1;let t=this.effectiveContextWindow(),n=I(e,this.estimateTokens),r=H(this.compactionConfig.trigger,t),i=this.compactionConfig.hardCeilingFraction??Ae,a=Math.max(r,Math.floor(t*i));return!!(n.bodyAfterPrefix>=r||n.total>=a||this.residencyPressure())}residencyPressure(){if(this.getEstimatedBytes==null)return!1;let e=this.getMaxContentBytes?.()??this.maxContentBytes;if(e==null)return!1;let t=this.getEstimatedBytes();return t!=null&&t>=e*.9}isEligibleForManualCompact(e){return _t(e,this.effectiveContextWindow(),this.compactionConfig,this.estimateTokens)}prepareCompaction(e,t,n){return vt(e,this.effectiveContextWindow(),this.compactionConfig,this.previousSummaries.get(X(t)),this.estimateTokens,n)}async compact(e,t,n){let r=await bt(e,this.summarize,{...this.compactionConfig,contextWindow:this.effectiveContextWindow(),...(()=>{let e=this.rawConfig.getCoverageThresholds?.();return{...e?.warn===void 0?{}:{coverageWarnThreshold:e.warn},...e?.error===void 0?{}:{coverageErrorThreshold:e.error},...e?.floor===void 0?{}:{coverageFloorThreshold:e.floor}}})()},n,this.estimateTokens,t,this.compactionSpill),i=Math.min(this.compactionConfig.coverageFloorThreshold??this.rawConfig.getCoverageThresholds?.()?.floor??Ke,this.compactionConfig.coverageErrorThreshold??Ge);if(r.coverage<i&&(Y.warn({sessionId:t,coverage:Number(r.coverage.toFixed(4)),floorThreshold:i},`[compaction] coverage below floor — boundary established WITH an explicit low-fidelity notice; a large portion of history is not represented in this summary (RFC-340 D1)`),r.summary=`${le(r.coverage)}\n\n${r.summary}`),t)try{await this.recordCompactionArchive(t,r,e.messagesToSummarize),Y.info(`Compaction recorded at ${r.archivePath}`)}catch(e){Y.warn({error:e},`Failed to record compaction archive`)}return this.previousSummaries.set(X(t),r.summary),r}async recordCompactionArchive(e,t,n){let r=(this.archiveIdCounter.get(e)??0)+1;this.archiveIdCounter.set(e,r),t.archivePath=`archive:${e}:${r}`,this.loadSessionEntries||await this.archiveStorage.archive(e,n,t.summary)}async process(e,t,r,i){let a=[...e],o=!1,s=!1,c,l,u,d,f=i?.force===!0,p=i?.skipPrune===!0,m=I(a,this.estimateTokens);if(!p&&(f||this.needsPruneWith(m,a))){let e=a.length,t=this.pruneWith(a,m,f);t.changed&&(a=t.messages,o=!0,d={prunedCount:t.prunedCount,tokensSaved:t.tokensSaved,messagesBefore:e,messagesAfter:a.length},Y.info(`Prune: removed ${t.prunedCount} tool outputs, saved ~${t.tokensSaved} tokens`))}let h=this.compactionConfig.enabled!==!1;if(f?h:this.needsCompaction(a)){let e=X(t),i=`${process.pid}:${this.clock.now()}`;if(!(this.compactionLock?await this.compactionLock.acquire(t??Dt,i):!this.compactionInFlight.has(e)))Y.info({sessionId:t},`Compaction already in flight for this session, skipping this round (will retry next round)`);else{this.compactionLock||this.compactionInFlight.add(e);try{let e=this.prepareCompaction(a,t,{force:f||this.residencyPressure()});if(e)try{let i=await this.compact(e,t,r);s=!0,c=i.summary,u=i.archivePath;let o={role:`user`,content:[{type:`text`,text:`${n}\n\n${u?ce(u,i.summary):i.summary}`}],timestamp:this.clock.now()};l=e.preservedMessages.map(Et),a=[o,...l],Y.info({sessionId:t,trigger:f?`force`:this.residencyPressure()?`residency`:`token`},`Compaction: ${e.messagesToSummarize.length} messages → summary, kept ${e.preservedMessages.length}`)}catch(e){Y.warn({error:e},`Compaction failed (LLM/summarizer error?), skipping compaction for this turn`)}}finally{this.compactionLock?await this.compactionLock.release(t??Dt,i).catch(()=>{}):this.compactionInFlight.delete(e)}}}return{messages:a,summary:c,replacement:l,archivePath:u,pruned:o,compacted:s,pruneMetrics:d}}async listArchives(e){if(this.loadSessionEntries&&this.messageFromEntry)try{return await this.listArchivesFromEntries(e)}catch(e){Y.warn({error:e},`listArchives from session entries failed, falling back`)}return this.archiveStorage.list(e)}async listArchivesFromEntries(e){let t=await this.loadSessionEntries(e),n=this.messageFromEntry,r=[],i=0;for(let a=0;a<t.length;a++){let o=t[a];if(o.type!==`compaction`)continue;let s=o.data;i++;let c=0,l=0;for(let e=a-1;e>=0;e--){let r=t[e];if(r.type===`compaction`)break;let i=n(r);i&&(c++,l=i.timestamp??l)}r.push({path:`archive:${e}:${i}`,timestamp:l||o.seq||0,messageCount:s.compactedCount,summary:s.summary.slice(0,200)})}return r}async readArchive(e){if(this.loadSessionEntries&&this.messageFromEntry)try{let t=await this.readArchiveFromEntries(e);if(t)return t}catch(e){Y.warn({error:e},`readArchive from session entries failed, falling back`)}return this.archiveStorage.read(e)}async readArchiveFromEntries(e){let t=e.match(/^archive:(.+):(\d+)$/);if(!t)return null;let n=t[1],r=parseInt(t[2],10),i=await this.loadSessionEntries(n),a=this.messageFromEntry,o=0,s=-1;for(let e=0;e<i.length;e++)if(i[e].type===`compaction`&&(o++,o===r)){s=e;break}if(s<0)return null;let c=i[s].data,l=-1;for(let e=s-1;e>=0;e--)if(i[e].type===`compaction`){l=e;break}let u=l+1,d=[];for(let e=u;e<s;e++){let t=a(i[e]);t&&d.push(t)}let f=this.clock.now();return J(d.filter(e=>e!==null),c.summary,f)}dispose(){this.persistent.dispose()}};function kt(e){return new Ot(e)}var At=class{baseUrl;userId;workspaceKeyGetter;isWorkspaceSyncEnabled;getAuth;fetchImpl;timeoutMs;local;onConflict;conflictCounters=new Map;knownVersions=new Map;constructor(e){this.baseUrl=e.baseUrl.replace(/\/+$/,``),this.userId=e.userId,this.workspaceKeyGetter=e.workspaceKey,this.isWorkspaceSyncEnabled=e.isWorkspaceSyncEnabled??(()=>!1),this.getAuth=e.getAuth,this.fetchImpl=e.fetch,this.timeoutMs=e.timeoutMs,this.local=new O(e.localDir,e.homeDirOverride),this.onConflict=e.onConflict}async load(e){let t=new Map,n=[];for(let r of e)if(this.userId&&jt(r.path)){let e=await this.loadRemoteUser(r.path);e===null?n.push(r):(t.set(r.path,e),this.local.write(r.path,e).catch(()=>{}))}else if(this.isWorkspaceSourceEnabled(r.path)){let e=await this.loadRemoteWorkspace(r.path);e===null?n.push(r):(t.set(r.path,e),this.local.write(r.path,e).catch(()=>{}))}else n.push(r);if(n.length>0){let e=await this.local.load(n);for(let[n,r]of e)t.set(n,r)}return t}async write(e,t){await this.local.write(e,t),this.userId&&jt(e)?this.writeRemoteUser(e,t).catch(()=>{}):this.isWorkspaceSourceEnabled(e)&&await this.writeRemoteWorkspace(e,t)}watch(e,t){return()=>{}}isWorkspaceSourceEnabled(e){return Mt(e)&&this.isWorkspaceSyncEnabled()&&!!this.workspaceKeyGetter?.()}remoteKey(e){let t=e.startsWith(`~/.otto/`)?e.slice(8):e;return`user:${this.userId}:${t}`}remoteResourceId(e){let t=e.startsWith(`~/.otto/`)?e.slice(8):e;return encodeURIComponent(t)}workspaceResourceId(e){let t=e.startsWith(`./.otto/`)?e.slice(8):e;return encodeURIComponent(t)}async loadRemoteUser(e){try{let t={Authorization:`Bearer ${(await this.getAuth()).token}`,Accept:`application/json`,"X-Otto-User-Id":this.userId},n=new AbortController,r=setTimeout(()=>n.abort(),this.timeoutMs);try{let r=await this.fetchImpl(`${this.baseUrl}/memory/user/${this.remoteResourceId(e)}`,{method:`GET`,headers:t,signal:n.signal});if(r.ok){let e=await r.json();if(typeof e.content==`string`)return e.content}if(r.status===404){let r=await this.fetchImpl(`${this.baseUrl}/sessions/${encodeURIComponent(this.remoteKey(e))}`,{method:`GET`,headers:t,signal:n.signal});if(r.ok){let e=await r.json();if(typeof e.content==`string`)return e.content}}}finally{clearTimeout(r)}}catch{}return null}async writeRemoteUser(e,t){try{let n={Authorization:`Bearer ${(await this.getAuth()).token}`,"Content-Type":`application/json`,"X-Otto-User-Id":this.userId},r=new AbortController,i=setTimeout(()=>r.abort(),this.timeoutMs);try{await this.fetchImpl(`${this.baseUrl}/memory/user/${this.remoteResourceId(e)}`,{method:`PUT`,headers:n,body:JSON.stringify({content:t}),signal:r.signal})}finally{clearTimeout(i)}}catch{}}async loadRemoteWorkspace(e){let t=this.workspaceKeyGetter?.();if(!t)return null;try{let n={Authorization:`Bearer ${(await this.getAuth()).token}`,Accept:`application/json`},r=new AbortController,i=setTimeout(()=>r.abort(),this.timeoutMs);try{let i=await this.fetchImpl(`${this.baseUrl}/memory/workspace/${encodeURIComponent(t)}/${this.workspaceResourceId(e)}`,{method:`GET`,headers:n,signal:r.signal});if(i.ok){let t=await i.json();if(typeof t.content==`string`)return typeof t.version==`number`&&this.knownVersions.set(e,t.version),t.content}}finally{clearTimeout(i)}}catch{}return null}async writeRemoteWorkspace(e,t){let n=this.workspaceKeyGetter?.();if(n)try{let r={Authorization:`Bearer ${(await this.getAuth()).token}`,"Content-Type":`application/json`},i=new AbortController,a=setTimeout(()=>i.abort(),this.timeoutMs),o=this.knownVersions.get(e)??0,s=o+1;try{let a=await this.fetchImpl(`${this.baseUrl}/memory/workspace/${encodeURIComponent(n)}/${this.workspaceResourceId(e)}`,{method:`PUT`,headers:r,body:JSON.stringify({content:t,version:s,expectedVersion:o}),signal:i.signal});if(a.status===409){await this.writeConflictSidecar(e,t);return}a.ok&&this.knownVersions.set(e,s)}finally{clearTimeout(a)}}catch{}}async writeConflictSidecar(e,t){let n=this.conflictCounters.get(e)??0,r=n%5;this.conflictCounters.set(e,n+1);let i=`${e}.conflict-${r}`;try{await this.local.write(i,t),this.onConflict?.(e,i)}catch{}}};function jt(e){return e===`~/.otto/AGENTS.md`||e===`~/.otto/MEMORY.md`||e.startsWith(`~/.otto/memory/`)}function Mt(e){return e===`./.otto/MEMORY.md`||e.startsWith(`./.otto/memory/`)&&!e.startsWith(`./.otto/local/`)||e===`./.otto/lessons.json`}function Nt(e){let t=new Map;return{async load(n){let r=n.filter(e=>!t.has(e.path)),i=r.length>0?await e.load(r):new Map;for(let e of n){let n=t.get(e.path);n!==void 0&&i.set(e.path,n)}return i},async write(e,n){t.set(e,n)}}}function Pt(e,t={}){let n=t.fileName??`AGENTS.md`,a=t.rootMarkers??[`.git`],s=t.maxDepth??25,l=t.maxFiles??10,u=t.maxFileBytes??64*1024,d=[],f=e;for(let e=0;e<s;e++){let e=c(f,n);try{r(e)&&i(e).size<=u&&d.push(e)}catch{}let t=a.some(e=>r(c(f,e))),s=o(f);if(t||s===f)break;f=s}return d.reverse().slice(-l).map(e=>({path:e,writable:!1}))}const Ft=e(`@x-otto/memory:auto`),Z=`# MEMORY`,It=/^- \[(.+?)\]\(memory\/(.+?)\.md\)/,Lt=`./.otto/AGENTS.md`;function Rt(e,t,n){return`<!-- otto-memory:start slug="${e}" -->\n## ${t.replace(/[\r\n]+/g,` `).trim()}\n\n${n.trim()}\n<!-- otto-memory:end slug="${e}" -->`}function zt(e){return RegExp(`<!-- otto-memory:start slug="${e}" -->[\\s\\S]*?<!-- otto-memory:end slug="${e}" -->`)}var Bt=class{store;baseDir;localBaseDir;index=``;localIndex=``;loaded=!1;localLoaded=!1;constructor(e){this.store=e.store,this.baseDir=(e.baseDir??`./.otto`).replace(/\/+$/,``),this.localBaseDir=(e.localBaseDir??`${this.baseDir}/local`).replace(/\/+$/,``)}get indexPath(){return`${this.baseDir}/MEMORY.md`}entryPathOf(e){return`${this.baseDir}/memory/${e}.md`}get localIndexPath(){return`${this.localBaseDir}/MEMORY.md`}localEntryPathOf(e){return`${this.localBaseDir}/memory/${e}.md`}asSource(){return{path:this.indexPath,writable:!0}}asLocalSource(){return{path:this.localIndexPath,writable:!0}}async load(){this.index=(await this.store.load([this.asSource()])).get(this.indexPath)??``,this.loaded=!0}async loadLocal(){this.localIndex=(await this.store.load([this.asLocalSource()])).get(this.localIndexPath)??``,this.localLoaded=!0}getInjection(){let e=this.index.trim();return e?`${e}\n\n> Auto-memory entries live in the memory store (\`${this.baseDir}/memory/\`), not workspace files — read them with the memory_read tool; record new durable facts with memory_record.`:``}getLocalInjection(){let e=this.localIndex.trim();return e?`${e}\n\n> Machine-specific memory entries live in \`${this.localBaseDir}/memory/\` — never synced to other devices. Read with memory_read; record with memory_record (scope: 'local').`:``}async record(e,t=`workspace`){if(t===`local`){await this.recordLocal(e);return}if(t===`project-shared`){await this.recordProjectShared(e);return}await this.ensureLoaded();let n=this.resolveSlug(e.name,this.indexLines());await this.store.write(this.entryPathOf(n),`# ${e.name}\n\n${e.content}\n`);let r=`- [${e.name}](memory/${n}.md) — ${e.description}`,i=this.indexLines().filter(e=>!e.includes(`(memory/${n}.md)`));i.some(e=>e.startsWith(Z))||i.unshift(Z),i.push(r),this.index=`${i.join(`
|
|
185
|
+
`)}\n`,await this.store.write(this.indexPath,this.index),Ft.info(`Auto-memory recorded: ${n}`)}async recordLocal(e){await this.ensureLocalLoaded();let t=this.resolveSlug(e.name,this.localIndexLines());await this.store.write(this.localEntryPathOf(t),`# ${e.name}\n\n${e.content}\n`);let n=`- [${e.name}](memory/${t}.md) — ${e.description}`,r=this.localIndexLines().filter(e=>!e.includes(`(memory/${t}.md)`));r.some(e=>e.startsWith(Z))||r.unshift(Z),r.push(n),this.localIndex=`${r.join(`
|
|
186
|
+
`)}\n`,await this.store.write(this.localIndexPath,this.localIndex),Ft.info(`Auto-memory recorded (local scope): ${t}`)}async recordProjectShared(e){let t=(await this.store.load([{path:Lt,writable:!0}])).get(Lt)??``,n=e.name.replace(/[\r\n]+/g,` `).trim(),r=RegExp(`<!-- otto-memory:start slug="([^"]+)" -->\\n## ${Vt(n)}\\n`).exec(t)?.[1]??Q(e.name),i=Rt(r,e.name,e.content),a=zt(r),o;o=a.test(t)?t.replace(a,i):t.trim()===``?`# 项目记忆(模型经 memory_record(scope: "project-shared") 写入,人工可编辑本文件任意内容)
|
|
187
|
+
\n${i}\n`:`${t.trimEnd()}\n\n${i}\n`,await this.store.write(Lt,o),Ft.info(`Auto-memory recorded (project-shared scope): ${r}`)}async read(e){await this.ensureLoaded();let t=this.slugByName(e,this.indexLines())??Q(e),n=this.entryPathOf(t),r=await this.readEntry(n);if(r!==null)return await this.withConflictLabel(n,r);await this.ensureLocalLoaded();let i=this.slugByName(e,this.localIndexLines())??Q(e),a=this.localEntryPathOf(i),o=await this.readEntry(a);return o===null?null:await this.withConflictLabel(a,o)}async withConflictLabel(e,t){for(let n=0;n<5;n++){let r=`${e}.conflict-${n}`;if(await this.readEntry(r)!==null)return`[CONFLICT] Concurrent edit detected for "${e}" — review "${r}" and reconcile manually.\n\n${t}`}return t}async readEntry(e){let t=(await this.store.load([{path:e,writable:!1}])).get(e);return t&&t.trim()!==``?t:null}async forget(e){await this.ensureLoaded(),await this.ensureLocalLoaded();let t=this.slugByName(e,this.indexLines())??Q(e);this.index=`${this.indexLines().filter(e=>!e.includes(`(memory/${t}.md)`)).join(`
|
|
194
188
|
`)}\n`,await this.store.write(this.indexPath,this.index),await this.store.write(this.entryPathOf(t),``);let n=this.slugByName(e,this.localIndexLines());n&&(this.localIndex=`${this.localIndexLines().filter(e=>!e.includes(`(memory/${n}.md)`)).join(`
|
|
195
189
|
`)}\n`,await this.store.write(this.localIndexPath,this.localIndex),await this.store.write(this.localEntryPathOf(n),``))}async ensureLoaded(){this.loaded||await this.load()}async ensureLocalLoaded(){this.localLoaded||await this.loadLocal()}indexLines(){return this.index.split(`
|
|
196
190
|
`).filter(e=>e.trim()!==``)}localIndexLines(){return this.localIndex.split(`
|
|
197
|
-
`).filter(e=>e.trim()!==``)}slugByName(e,t){for(let n of t){let t=
|
|
191
|
+
`).filter(e=>e.trim()!==``)}slugByName(e,t){for(let n of t){let t=It.exec(n);if(t&&t[1]===e)return t[2]??null}return null}resolveSlug(e,t){let n=this.slugByName(e,t);if(n)return n;let r=new Map;for(let e of t){let t=It.exec(e);t&&t[1]!==void 0&&t[2]!==void 0&&r.set(t[2],t[1])}let i=Q(e),a=i,o=2;for(;r.has(a)&&r.get(a)!==e;)a=`${i}-${o}`,o+=1;return a}};function Q(e){return e.toLowerCase().replace(/[^a-z0-9一-鿿]+/g,`-`).replace(/^-+|-+$/g,``)}function Vt(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}const Ht=e(`@x-otto/memory:extractor`);async function Ut(e,t,n,r={}){let i=Gt(e);if(!i.trim())return[];let a;try{a=await n(se(i),{maxTokens:r.maxTokens??2048,signal:r.signal})}catch(e){return Ht.debug(`extraction LLM call failed (fail-closed → []): ${String(e)}`),[]}let o=Kt(a);if(!o)return[];let s=r.maxLessons??5,c=[];for(let e of o){let n=qt(e,t);if(n&&c.push(n),c.length>=s)break}return c}async function Wt(e,t,n={}){let r=[],i=0;for(let a of t){let{lesson:t,deduped:o}=await e.saveDeduped(a,n.similarityThreshold);o?i++:r.push(t)}return{saved:r,skippedSimilar:i}}function Gt(e){return e.map(e=>`${e.role===`user`?`Human`:e.role===`assistant`?`Assistant`:e.role===`tool_result`?`Tool[${e.toolName??`unknown`}]`:`System`}: ${N(e)}`).join(`
|
|
198
192
|
|
|
199
|
-
`)}function
|
|
193
|
+
`)}function Kt(e){let t=e.indexOf(`[`),n=e.lastIndexOf(`]`);if(t===-1||n===-1||n<t)return null;try{let r=JSON.parse(e.slice(t,n+1));return Array.isArray(r)?r:null}catch{return null}}function qt(e,t){if(!e||typeof e!=`object`)return null;let n=typeof e.trigger==`string`?e.trigger.trim():``,r=typeof e.insight==`string`?e.insight.trim():``;if(!n||!r)return null;let i=Array.isArray(e.tags)?e.tags.filter(e=>typeof e==`string`&&e.trim().length>0).map(e=>e.trim().toLowerCase()).slice(0,5):[];return{trigger:n.slice(0,500),insight:r.slice(0,2e3),tags:i,sourceSessionId:t}}const $=e(`@x-otto/memory:auto-extract`);function Jt(e){return e===`learn`||e===`memory_record`}function Yt(e,t){for(let n of e)if(n.role===`assistant`){for(let e of n.content)if(e.type===`tool_call`&&t(e.name))return!0}return!1}function Xt(e){let t=new Map,n=e.isMemoryWriteTool??Jt,r=()=>Math.max(1,e.config.everyNTurns??1);function i(e){let n=t.get(e);return n||(n={lastExtractedIndex:0,idleCount:0},t.set(e,n)),n}let a=(e=0,t=0,n)=>({ran:n===void 0,reason:n,saved:e,skippedSimilar:t});return{async onIdle(t,o,s){if(!e.config.enabled)return a(0,0,`disabled`);let c=i(t),l=o.slice(c.lastExtractedIndex);if(l.length===0)return a(0,0,`no-new-messages`);if(c.idleCount+=1,c.idleCount<r())return a(0,0,`throttled`);let u=e.config.minRelevantHits;if(u!==void 0&&(e.readRelevantHits?await e.readRelevantHits():-1)<u)return e.readRelevantHits||$.warn(`auto-extract minRelevantHits=${u} set but readRelevantHits port missing — failing closed (no extraction) to avoid bypassing the configured threshold`),a(0,0,`below-relevant-hits-low-water`);let d=()=>{c.lastExtractedIndex=o.length,c.idleCount=0};if(Yt(l,n))return d(),a(0,0,`main-agent-wrote`);try{let n=await Ut(l,t,e.llm,{maxTokens:e.config.maxTokens,maxLessons:e.config.maxLessons,signal:s?.signal}),r=await Wt(e.store,n,{similarityThreshold:e.config.similarityThreshold});return d(),n.length>0&&$.info({sessionId:t,extracted:n.length,saved:r.saved.length,deduped:r.skippedSimilar},`memory-auto-extract`),a(r.saved.length,r.skippedSimilar)}catch(e){return $.warn(`auto-extract failed (fail-closed): ${String(e)}`),a(0,0,`error`)}},reset(e){t.delete(e)}}}const Zt=new Set([`node_modules`,`.git`,`dist`,`.next`,`.turbo`,`coverage`,`.cache`,`__pycache__`,`.vscode`]);var Qt=class{lastSnapshot=null;async capture(e){let[t,n]=await Promise.all([this.buildDirectoryTree(e.workspaceDir,0),this.checkRecentFiles(e.workspaceDir,e.recentFiles??[])]),r=[];n.length>0&&r.push(`${n.length} recently modified file(s) detected`);let i={timestamp:Date.now(),directoryTree:t,modifiedFiles:n,planStatus:e.planStatus,findings:r};return this.lastSnapshot=i,i}getLastSnapshot(){return this.lastSnapshot}formatSnapshot(e){let t=[`[File State Snapshot @ ${new Date(e.timestamp).toISOString()}]`];if(e.directoryTree&&t.push(`Directory:\n${e.directoryTree}`),e.modifiedFiles.length>0){let n=e.modifiedFiles.map(e=>` ${e.action}: ${e.path} — ${e.summary}`).join(`
|
|
200
194
|
`);t.push(`Modified Files:\n${n}`)}return e.planStatus&&t.push(`Plan Status: ${e.planStatus}`),e.findings.length>0&&t.push(`Findings:\n${e.findings.map(e=>` - ${e}`).join(`
|
|
201
195
|
`)}`),t.join(`
|
|
202
196
|
|
|
203
|
-
`)}async buildDirectoryTree(e,t,n=``){if(t>=4)return n?`${n}...\n`:``;let r;try{r=(await u(e,{withFileTypes:!0})).filter(e=>!
|
|
204
|
-
`)}async checkRecentFiles(e,t){let n=[],r=Date.now();for(let i of t){let t=i.startsWith(`/`)?i:c(e,i),a=l(e,t);try{let e=await d(t),i=r-e.mtimeMs,o=e.birthtimeMs>0&&e.mtimeMs-e.birthtimeMs<1e3?`created`:`modified`,s=`${(e.size/1024).toFixed(1)}KB, modified ${
|
|
197
|
+
`)}async buildDirectoryTree(e,t,n=``){if(t>=4)return n?`${n}...\n`:``;let r;try{r=(await u(e,{withFileTypes:!0})).filter(e=>!Zt.has(e.name)&&!e.name.startsWith(`.`)).sort((e,t)=>e.isDirectory()===t.isDirectory()?e.name.localeCompare(t.name):e.isDirectory()?-1:1).slice(0,50).map(e=>e.name+(e.isDirectory()?`/`:``))}catch{return``}let i=[];for(let a of r){let r=a.endsWith(`/`);if(i.push(`${n}${a}`),r){let r=await this.buildDirectoryTree(c(e,a.slice(0,-1)),t+1,n+` `);r&&i.push(r.trimEnd())}}return i.join(`
|
|
198
|
+
`)}async checkRecentFiles(e,t){let n=[],r=Date.now();for(let i of t){let t=i.startsWith(`/`)?i:c(e,i),a=l(e,t);try{let e=await d(t),i=r-e.mtimeMs,o=e.birthtimeMs>0&&e.mtimeMs-e.birthtimeMs<1e3?`created`:`modified`,s=`${(e.size/1024).toFixed(1)}KB, modified ${$t(i)} ago`;n.push({path:a,action:o,summary:s})}catch{n.push({path:a,action:`deleted`,summary:`file not found`})}}return n}};function $t(e){let t=Math.floor(e/1e3);if(t<60)return`${t}s`;let n=Math.floor(t/60);return n<60?`${n}m`:`${Math.floor(n/60)}h`}var en=class{records=[];now;persist;logWarn;constructor(e={}){this.now=e.now??(()=>Date.now()),this.persist=e.persist,this.logWarn=e.logWarn}append(e){let t={...e,at:this.now()};this.records.push(t),this.persist&&this.persist.append(t).catch(e=>{this.logWarn?.(`Failed to persist evolution signal`,e)})}projectRelevantHits(e){return e.map(e=>({kind:`relevant.hits`,key:e.id,value:e.relevantHitCount??0}))}appendRelevantHits(e){for(let t of this.projectRelevantHits(e))this.append(t)}list(){return this.records}counts(){let e=new Map;for(let t of this.records){let n=`${t.kind}:${t.key}`,r=e.get(n)??{kind:t.kind,key:t.key,total:0,records:0,lastAt:t.at};r.total+=t.value,r.records+=1,r.lastAt=t.at,e.set(n,r)}return[...e.values()]}async restoreFromLog(){if(!this.persist)return 0;try{let e=await this.persist.load();return this.records.length=0,this.records.push(...e),e.length}catch(e){return this.logWarn?.(`Failed to restore evolution signal log; starting empty`,e),0}}async close(){await this.persist?.close?.()}get size(){return this.records.length}};const tn=`signals`,nn={encode:e=>JSON.stringify(e),decode:e=>JSON.parse(e)};function rn(e){let t=new f({baseDir:e,extension:`.jsonl`,codec:nn});return{async append(e){return t.append(tn,e)},load:async()=>{let e=[];for await(let{entry:n}of t.read(tn))e.push(n);return e},async close(){await t.close?.()}}}const an=.6;function on(e){return new Set(e.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(e=>e.length>2))}function sn(e,t){if(e.size===0||t.size===0)return 0;let n=0;for(let r of e)t.has(r)&&n++;return n/(e.size+t.size-n)}const cn=new Set([1,2]);function ln(e){let t=`[${e.tags.join(`, `)}] ${e.trigger} → ${e.insight}`,n=0;for(let e of t)/[\u3000-\u9fff\uff00-\uffef]/.test(e)&&n++;return Math.ceil(n+(t.length-n)/4)}function un(e){let t=JSON.stringify({tags:[...e.tags].sort(),trigger:e.trigger,insight:e.insight}),n=2166136261;for(let e=0;e<t.length;e++)n^=t.charCodeAt(e),n=Math.imul(n,16777619)>>>0;return n.toString(16).padStart(8,`0`)}function dn(e,t){let n=e.relevantHitCount??e.appliedCount??0;return{...e,scope:e.scope??t,state:e.state??`active`,contentHash:e.contentHash??un(e),estimatedTokens:e.estimatedTokens??ln(e),relevantHitCount:n,coreInjectedCount:e.coreInjectedCount??0,hitCountTotal:e.hitCountTotal??n,appliedCount:n}}function fn(e){if(!e||typeof e!=`object`)return;let t=e;if(t.version!==void 0&&cn.has(t.version)&&t.data)return t.data;let n=e;if(n.version===void 0&&Array.isArray(n.lessons))return e}var pn=class{lessons=new Map;nextId=1;loaded=!1;snapshotId;persistence;store;storePath;logger;scope;ephemeral;constructor(e){if(this.logger=e?.logger,this.scope=e?.scope,this.ephemeral=e?.ephemeral??!1,e?.store)this.store=e.store,this.storePath=e.storePath??e.path;else if(e?.path){let t=s(e.path)||`.json`;this.snapshotId=a(e.path,t),this.persistence=p({type:`file`,baseDir:o(e.path),extension:t})}}async save(e){await this.ensureLoaded();let t=`lesson_${this.nextId++}`,n=dn({...e,id:t,createdAt:Date.now(),appliedCount:0},this.scope);return this.lessons.set(t,n),await this.persist(),n}async search(e,t=5){await this.ensureLoaded();let n=e.toLowerCase(),r=[];for(let e of this.lessons.values()){let t=0;e.trigger.toLowerCase().includes(n)&&(t+=3),e.insight.toLowerCase().includes(n)&&(t+=2);for(let r of e.tags)r.toLowerCase().includes(n)&&(t+=1);t>0&&r.push({lesson:e,score:t})}let i=r.sort((e,t)=>t.score-e.score).slice(0,t).map(e=>(e.lesson.relevantHitCount=(e.lesson.relevantHitCount??0)+1,e.lesson.hitCountTotal=(e.lesson.hitCountTotal??0)+1,e.lesson.lastAccessedAt=Date.now(),e.lesson.appliedCount=e.lesson.relevantHitCount,e.lesson));return await this.persist(),i}async findSimilar(e,t,n=an){await this.ensureLoaded();let r=on(`${e} ${t}`);if(r.size===0)return;let i;for(let e of this.lessons.values()){let t=sn(r,on(`${e.trigger} ${e.insight}`));t>=n&&(!i||t>i.similarity)&&(i={lesson:e,similarity:t})}return i}async saveDeduped(e,t=an){let n=await this.findSimilar(e.trigger,e.insight,t);return n?{lesson:n.lesson,deduped:!0}:{lesson:await this.save(e),deduped:!1}}async list(e){await this.ensureLoaded();let t=[...this.lessons.values()];if(e?.tags&&e.tags.length>0){let n=new Set(e.tags);t=t.filter(e=>e.tags.some(e=>n.has(e)))}if(e?.query){let n=e.query.toLowerCase();t=t.filter(e=>e.trigger.toLowerCase().includes(n)||e.insight.toLowerCase().includes(n))}return t}async get(e){return await this.ensureLoaded(),this.lessons.get(e)}async recordRelevantHits(e,t=Date.now()){await this.ensureLoaded();let n=!1;for(let r of e){let e=this.lessons.get(r);e&&(e.relevantHitCount=(e.relevantHitCount??0)+1,e.hitCountTotal=(e.hitCountTotal??0)+1,e.lastAccessedAt=t,e.appliedCount=e.relevantHitCount,n=!0)}n&&await this.persist()}async delete(e){await this.ensureLoaded();let t=this.lessons.delete(e);return t&&await this.persist(),t}get size(){return this.lessons.size}async ensureLoaded(){if(!this.loaded){if(this.store&&this.storePath){await this.loadFromStore(),this.loaded=!0;return}if(!this.persistence||!this.snapshotId){this.loaded=!0;return}try{let e=fn(await this.persistence.load(this.snapshotId));if(e){for(let t of e.lessons)this.lessons.set(t.id,dn(t,this.scope));this.nextId=e.nextId??this.lessons.size+1}}catch(e){this.logger?.warn(`Failed to load lessons from file persistence (snapshotId=${this.snapshotId}): ${String(e)}. Falling back to empty lessons — all previously learned lessons will be lost for this session.`)}this.loaded=!0}}async refresh(){!this.store||!this.storePath||(await this.loadFromStore(),this.loaded=!0)}async loadFromStore(){try{let e=(await this.store.load([{path:this.storePath,writable:!0}])).get(this.storePath);if(!e)return;let t=fn(JSON.parse(e));if(t){for(let e of t.lessons)this.lessons.set(e.id,dn(e,this.scope));this.nextId=t.nextId??this.lessons.size+1}}catch(e){this.logger?.warn(`Failed to load lessons from MemoryStore (storePath=${this.storePath}): ${String(e)}. Falling back to empty lessons — all previously learned lessons will be lost for this session.`)}}async persist(){if(this.ephemeral)return;if(this.store&&this.storePath){let e={lessons:[...this.lessons.values()],nextId:this.nextId};await this.store.write(this.storePath,JSON.stringify({version:2,data:e}));return}if(!this.persistence||!this.snapshotId)return;let e=Date.now();await m(this.persistence,this.snapshotId,{lessons:[...this.lessons.values()],nextId:this.nextId},{version:2,tags:[`operational-learning`],now:e,preserveCreatedAt:!1})}};export{Bt as AutoMemory,B as DEFAULT_COMPACTION_CONFIG,T as DEFAULT_MEMORY_INJECTION_BUDGET_TOKENS,ue as DEFAULT_MEMORY_SOURCES,V as DEFAULT_PRUNE_CONFIG,an as DEFAULT_SIMILARITY_THRESHOLD,en as EvolutionSignalStore,Qt as FileStateManager,O as FileSystemMemoryStore,At as HttpMemoryStore,Se as IMAGE_BLOCK_TOKEN_ESTIMATE,Tt as InMemoryArchiveStorage,ye as InMemoryMemoryStore,oe as LESSON_EXTRACTION_PROMPT,Ot as MemoryManager,pn as OperationalLearningStore,ve as PersistentMemory,S as SEGMENT_MERGE_PROMPT,x as SEGMENT_SUMMARIZATION_PROMPT,rt as SPILL_MIN_CHARS,h as SUMMARIZATION_PROMPT,_ as TURN_PREFIX_SUMMARIZATION_PROMPT,g as UPDATE_SUMMARIZATION_PROMPT,b as WITH_PRESERVED_TAIL_PROMPT,ce as buildArchiveReference,se as buildExtractionPrompt,le as buildLowCoverageNotice,ae as buildMemoryInjection,te as buildSegmentMergePrompt,C as buildSummarizationPrompt,w as buildTurnPrefixPrompt,un as computeLessonContentHash,z as computeMemoryDefaults,Xt as createMemoryAutoExtractor,kt as createMemoryManager,Nt as createOverlayMemoryStore,rn as createSignalLog,Pt as discoverAgentsSources,xe as ensureTokenizer,ln as estimateLessonTokens,P as estimateMessageTokens,F as estimateMessagesTokens,M as estimateTokens,j as estimateTokensHeuristic,Ut as extractLessons,J as formatArchive,N as messageToText,Ye as prune,H as resolveContextSize,Wt as saveExtractedLessons,it as spillBatchP3};
|
|
205
199
|
//# sourceMappingURL=index.js.map
|