agentix-cli 0.45.1 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-YC3VJVPK.js → agent-LI3AZQBC.js} +2 -2
- package/dist/{board-dashboard-U5YP4HWE.js → board-dashboard-IE2P6QFU.js} +2 -2
- package/dist/{board-dashboard-U5YP4HWE.js.map → board-dashboard-IE2P6QFU.js.map} +1 -1
- package/dist/chunk-CTCOWVNL.js +241 -0
- package/dist/chunk-CTCOWVNL.js.map +1 -0
- package/dist/chunk-DP7RTNDM.js +24 -0
- package/dist/chunk-DP7RTNDM.js.map +1 -0
- package/dist/{chunk-SAFAI4AV.js → chunk-E6EW42EA.js} +2 -2
- package/dist/chunk-FRHSQVFN.js +3 -0
- package/dist/chunk-FRHSQVFN.js.map +1 -0
- package/dist/chunk-GAUQOIEE.js +160 -0
- package/dist/chunk-GAUQOIEE.js.map +1 -0
- package/dist/{chunk-3FIS6JD3.js → chunk-NVVXVUQU.js} +2 -2
- package/dist/{chunk-MDAC3PKR.js → chunk-Q6EBTCEZ.js} +160 -399
- package/dist/chunk-Q6EBTCEZ.js.map +1 -0
- package/dist/chunk-RY2EULHS.js +2 -0
- package/dist/chunk-RY2EULHS.js.map +1 -0
- package/dist/chunk-SGFXE5FP.js +109 -0
- package/dist/chunk-SGFXE5FP.js.map +1 -0
- package/dist/{chunk-KZYNQRQQ.js → chunk-Y3SVZ6VS.js} +124 -122
- package/dist/chunk-Y3SVZ6VS.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/program-BIMB44HN.js +2 -0
- package/dist/promote-R6WVBMT7.js +2 -0
- package/dist/proposals-MWMEXRDW.js +2 -0
- package/dist/registry-STML35CV.js +2 -0
- package/dist/registry-STML35CV.js.map +1 -0
- package/dist/review-candidates-ROFQR6XE.js +4 -0
- package/dist/review-candidates-ROFQR6XE.js.map +1 -0
- package/dist/wiki-LGHX3WPI.js +2 -0
- package/dist/wiki-LGHX3WPI.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-3RKIBJG6.js +0 -182
- package/dist/chunk-3RKIBJG6.js.map +0 -1
- package/dist/chunk-KZYNQRQQ.js.map +0 -1
- package/dist/chunk-MDAC3PKR.js.map +0 -1
- package/dist/chunk-MTVRYTLL.js +0 -109
- package/dist/chunk-MTVRYTLL.js.map +0 -1
- package/dist/program-WLPVWTAT.js +0 -2
- package/dist/registry-EESWAPMR.js +0 -2
- package/dist/review-candidates-BFAWNDOR.js +0 -4
- package/dist/review-candidates-BFAWNDOR.js.map +0 -1
- package/dist/wiki-5JORBO4G.js +0 -2
- /package/dist/{agent-YC3VJVPK.js.map → agent-LI3AZQBC.js.map} +0 -0
- /package/dist/{chunk-SAFAI4AV.js.map → chunk-E6EW42EA.js.map} +0 -0
- /package/dist/{chunk-3FIS6JD3.js.map → chunk-NVVXVUQU.js.map} +0 -0
- /package/dist/{program-WLPVWTAT.js.map → program-BIMB44HN.js.map} +0 -0
- /package/dist/{registry-EESWAPMR.js.map → promote-R6WVBMT7.js.map} +0 -0
- /package/dist/{wiki-5JORBO4G.js.map → proposals-MWMEXRDW.js.map} +0 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import{q as L,r as F}from"./chunk-3SCPRYJB.js";import{b as _,c as $,d as E}from"./chunk-FRHSQVFN.js";import{b as C}from"./chunk-DP7RTNDM.js";import{e as S}from"./chunk-SGFXE5FP.js";import{b as j}from"./chunk-64P4QHSZ.js";import{createHash as Z}from"crypto";import{existsSync as J,readdirSync as Q,readFileSync as ee,writeFileSync as te}from"fs";import{resolve as A}from"path";function he(n,t,o,e,s,i,a=""){return V(t,o,e,s,i,a)}function V(n,t,o,e,s,i=""){let a=t?`
|
|
2
|
+
## Worldview
|
|
3
|
+
|
|
4
|
+
${t}
|
|
5
|
+
`:"",l=new Map;for(let g of o){let p=g.type||"untyped",c=l.get(p)||[];c.push(g),l.set(p,c)}let u=o.length===0?"":`
|
|
6
|
+
## Existing Articles (the current catalog)
|
|
7
|
+
|
|
8
|
+
`+Array.from(l.entries()).sort().map(([g,p])=>`**${g}**
|
|
9
|
+
`+p.sort((c,r)=>c.title.localeCompare(r.title)).map(c=>`- [[${c.title}]] \u2014 ${c.path}`).join(`
|
|
10
|
+
`)).join(`
|
|
11
|
+
|
|
12
|
+
`)+`
|
|
13
|
+
`;return`You are compiling a personal wiki for the "${n}" agent in the Karpathy / Farzapedia pattern.
|
|
14
|
+
|
|
15
|
+
## The Pattern (read this first \u2014 it shapes every decision below)
|
|
16
|
+
|
|
17
|
+
The wiki is a compounding knowledge base of interlinked markdown articles that a future LLM will read by walking the graph, NOT by bag-of-words search. Your job is to produce articles that another agent can navigate via \`type\` + wikilinks. If the wikilinks don't form a coherent graph, the wiki is useless regardless of how many tags you add.
|
|
18
|
+
|
|
19
|
+
**Three rules, in priority order:**
|
|
20
|
+
|
|
21
|
+
1. **\`type\` is the organizational spine.** Every article has exactly one of: \`person | project | place | concept | event | decision | pattern\`. Choose the type BEFORE writing; if you can't pick one, the article probably isn't needed.
|
|
22
|
+
2. **\`related\` is the navigation surface.** Every article cross-references 2\u20135 other articles via \`[[Article Title]]\` wikilinks in the body AND lists them in the \`related\` frontmatter field. Links the reader could follow to learn more.
|
|
23
|
+
3. **Tags are a secondary hint, not a retrieval spine.** 2\u20134 specific tags max. Do NOT tag aggressively. Do NOT tag with dates unless the article is an \`event\`. Do NOT tag with generic terms ("deploy", "work") that will match half the corpus.
|
|
24
|
+
|
|
25
|
+
## Path convention
|
|
26
|
+
|
|
27
|
+
Path reflects type: \`<type>s/<slug>.md\` where slug is a kebab-case title.
|
|
28
|
+
|
|
29
|
+
- People: \`people/jane-doe.md\`
|
|
30
|
+
- Projects: \`projects/globex-system-v2.md\`
|
|
31
|
+
- Concepts: \`concepts/staging-deployment.md\`
|
|
32
|
+
- Events: \`events/YYYY-MM-DD-<slug>.md\` (date in path)
|
|
33
|
+
- Decisions: \`decisions/<slug>.md\`
|
|
34
|
+
- Patterns: \`patterns/<slug>.md\`
|
|
35
|
+
- Places: \`places/<slug>.md\`
|
|
36
|
+
|
|
37
|
+
## How to process entries
|
|
38
|
+
|
|
39
|
+
For each of the ${s} raw entries below, ask in this order:
|
|
40
|
+
|
|
41
|
+
1. **Does it extend an existing article?** If a person/project/concept mentioned in the entry already has an article in the catalog, produce an UPDATE with the full merged content. Prefer merging over proliferating.
|
|
42
|
+
2. **Does it deserve a new article?** Only if the subject is a persistent entity (a person, a project, a recurring concept, a specific event/decision) that future queries will need to find. Not every conversation deserves an article.
|
|
43
|
+
3. **Does it belong in an existing \`event\` or \`decision\`?** Most work entries fold into one of these.
|
|
44
|
+
4. **Can you skip it?** If the entry is small talk, a transient status ping, or already covered elsewhere \u2014 skip. The wiki is curated, not exhaustive.
|
|
45
|
+
|
|
46
|
+
## Writing standards
|
|
47
|
+
|
|
48
|
+
- **Wikipedia-style, flat, factual, encyclopedic.** This is the agent's knowledge, not a diary. Write about the *role* of the entity in our work, not a product description.
|
|
49
|
+
- **Synthesize, don't quote.** At most 2 short quoted lines per article.
|
|
50
|
+
- **Organize by theme, not chronology.** An article about a person lists what they do, who they work with, and their actual contact details \u2014 not a log of every interaction. History goes last, and only where it still bears on the present.
|
|
51
|
+
- **Length: 20\u2013100 lines.** Articles exceeding 100 lines should split into multiple type-specific articles.
|
|
52
|
+
- **Every paragraph earns its place.** Cut narrative filler. If you can remove a sentence without losing a fact, remove it.
|
|
53
|
+
|
|
54
|
+
## Build in layers
|
|
55
|
+
|
|
56
|
+
Facts are not equally load-bearing. Write them in this order, and never
|
|
57
|
+
spend length on a lower layer while a higher one is missing:
|
|
58
|
+
|
|
59
|
+
${F()}
|
|
60
|
+
|
|
61
|
+
An article is a building, not a transcript. The common failure is to
|
|
62
|
+
furnish a slab: a person's page that records an invoice thread in detail
|
|
63
|
+
and never states their role, their organisation, or how to reach them.
|
|
64
|
+
That article cannot answer "who is this" or "how do I reach them" \u2014 the
|
|
65
|
+
two questions it exists for. History is the last layer, and it is the
|
|
66
|
+
only one you can safely leave thin.
|
|
67
|
+
|
|
68
|
+
## Identity before history
|
|
69
|
+
|
|
70
|
+
An entity article must first establish **what the entity is**, and only then
|
|
71
|
+
what happened to it.
|
|
72
|
+
|
|
73
|
+
Before the narrative, every entity article carries an **Identity** section
|
|
74
|
+
with the fields for its type. State a field even when the entries do not
|
|
75
|
+
answer it, as \`unknown\`, so the gap is visible and a later pass can fill it.
|
|
76
|
+
A field silently omitted is indistinguishable from a field that does not
|
|
77
|
+
apply.
|
|
78
|
+
|
|
79
|
+
${L()}
|
|
80
|
+
|
|
81
|
+
Fields in **bold** are the ones an article of that type is unusable without.
|
|
82
|
+
Every article of any type also states what it is, why it matters to our work,
|
|
83
|
+
and which named people, projects or systems it connects to.
|
|
84
|
+
|
|
85
|
+
Extract identifiers **verbatim** from the entries. A phone number, an
|
|
86
|
+
account number, a URL or a path is the single most useful thing an article
|
|
87
|
+
can carry, and it is exactly what summarising discards. Never paraphrase or
|
|
88
|
+
redact one that appears in the source; if a value appears only in part, say
|
|
89
|
+
so rather than dropping it.
|
|
90
|
+
|
|
91
|
+
## Wikilink discipline
|
|
92
|
+
|
|
93
|
+
- Use \`[[Article Title]]\` **inside the body** every time another tracked entity is referenced.
|
|
94
|
+
- List those same targets in the \`related\` frontmatter field.
|
|
95
|
+
- If you reference something that has no article yet (\`[[New Thing]]\`), add "New Thing" to the output \`gaps\` array so the next pass can create it.
|
|
96
|
+
|
|
97
|
+
## Article frontmatter (exact shape \u2014 do not invent fields)
|
|
98
|
+
|
|
99
|
+
\`\`\`yaml
|
|
100
|
+
---
|
|
101
|
+
title: "Article Title"
|
|
102
|
+
type: person | project | place | concept | event | decision | pattern
|
|
103
|
+
related: ["Other Article", "Another Article"]
|
|
104
|
+
tags: ["2-4-specific-tags", "no-dates-unless-event"]
|
|
105
|
+
owner: ${n}
|
|
106
|
+
access: public | shared | private
|
|
107
|
+
created: YYYY-MM-DD
|
|
108
|
+
last_updated: YYYY-MM-DD
|
|
109
|
+
sources: ["entry-id-1", "entry-id-2"]
|
|
110
|
+
---
|
|
111
|
+
\`\`\`
|
|
112
|
+
|
|
113
|
+
Access guidance: default \`public\`; \`private\` only for sensitive credentials or agent-specific learnings; \`shared\` with specific agent IDs when the article matters only to a subset.
|
|
114
|
+
${a}${u}${i}
|
|
115
|
+
## Gap Detection
|
|
116
|
+
|
|
117
|
+
After compiling, populate a \`gaps\` array: wikilink targets you referenced but for which no article exists yet. Be specific:
|
|
118
|
+
|
|
119
|
+
- Good: "Karim Rahmouni \u2014 coach for Tunis Padel club, mentioned twice but no \`people/\` article"
|
|
120
|
+
- Bad: "We need more content about deployments"
|
|
121
|
+
|
|
122
|
+
## Output \u2014 valid JSON only, no markdown fencing
|
|
123
|
+
|
|
124
|
+
\`\`\`
|
|
125
|
+
{
|
|
126
|
+
"articles": [
|
|
127
|
+
{
|
|
128
|
+
"path": "projects/globex-system-v2.md",
|
|
129
|
+
"title": "Globex System V2",
|
|
130
|
+
"type": "project",
|
|
131
|
+
"related": ["Alex Rivera", "Laravel", "Staging Deployment"],
|
|
132
|
+
"tags": ["globex", "laravel", "react"],
|
|
133
|
+
"content": "Globex System V2 is [[Alex Rivera]]'s production Laravel + React app for \u2026",
|
|
134
|
+
"sources": ["entry-id-1", "entry-id-2"]
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
"gaps": [
|
|
138
|
+
"Karim Rahmouni \u2014 Tunis Padel coach, mentioned in entries but no people/ article"
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
\`\`\`
|
|
142
|
+
|
|
143
|
+
ENTRIES (${s}):
|
|
144
|
+
|
|
145
|
+
${e}`}var Y=2e3,X=300;function O(n,t,o,e){let s=e?`
|
|
146
|
+
## Worldview
|
|
147
|
+
|
|
148
|
+
${e}
|
|
149
|
+
`:"",i=new Map;for(let p of o){let c=p.type||"untyped",r=i.get(c)||[];r.push(p),i.set(c,r)}let a=o.length>X,l=o.length===0?"":`
|
|
150
|
+
## Existing Articles (the current catalog${a?`, ${o.length} articles \u2014 titles only`:""})
|
|
151
|
+
|
|
152
|
+
`+Array.from(i.entries()).sort().map(([p,c])=>`**${p}**
|
|
153
|
+
`+c.sort((r,d)=>r.title.localeCompare(d.title)).map(r=>a?`- [[${r.title}]]`:`- [[${r.title}]] \u2014 ${r.path}`).join(`
|
|
154
|
+
`)).join(`
|
|
155
|
+
|
|
156
|
+
`)+`
|
|
157
|
+
`,u=t.reduce((p,c)=>p+c.candidates.length,0),g=t.map(p=>{let c=p.corroboratingAgents.length>1?`corroborated by: ${p.corroboratingAgents.join(", ")} (${p.corroboratingAgents.length} agents)`:`single agent: ${p.corroboratingAgents[0]}`;return p.candidates.map(r=>{let d=r.memory.body.length>Y?r.memory.body.slice(0,Y)+`
|
|
158
|
+
[\u2026 truncated]`:r.memory.body;return[`--- CANDIDATE ${r.stamp} ---`,`type: ${r.memory.type} | agent: ${r.agentId} | ${c} | confidence: ${p.confidence.toFixed(2)}`,`description: ${r.memory.description}`,d,"--- END CANDIDATE ---"].join(`
|
|
159
|
+
`)}).join(`
|
|
160
|
+
|
|
161
|
+
`)}).join(`
|
|
162
|
+
|
|
163
|
+
`);return`You are promoting per-agent experiential memories into the shared, authoritative AgentX wiki (owner: "${n}").
|
|
164
|
+
|
|
165
|
+
## The Job
|
|
166
|
+
|
|
167
|
+
Memory is "what one agent learned"; the wiki is "what every agent should know." For each candidate memory below, decide: promote it into a wiki article, or skip it with a reason.
|
|
168
|
+
|
|
169
|
+
**The promotion test \u2014 apply in order, skip on the first "no":**
|
|
170
|
+
|
|
171
|
+
1. **Durable?** Will this still be true in 3 months? Transient state, one-off task context, and dated status pings fail here.
|
|
172
|
+
2. **Cross-agent?** Would a DIFFERENT agent benefit from knowing this? Facts about shared infrastructure, projects, people, and conventions pass; one agent's private workflow habits fail.
|
|
173
|
+
3. **A fact, not a preference?** Personal style preferences private to one agent's operator relationship fail.
|
|
174
|
+
4. **Never a secret.** Credentials, tokens, keys, and passwords are NEVER promoted \u2014 skip with reason "contains secret". Pointers to where a secret lives (e.g. "SSH config is in X") are fine; the secret itself is not.
|
|
175
|
+
|
|
176
|
+
Corroboration across agents (shown per candidate) is a confidence signal, not a requirement \u2014 a single-agent memory that passes the test above should be promoted.
|
|
177
|
+
|
|
178
|
+
**Three rules for the articles you write, in priority order:**
|
|
179
|
+
|
|
180
|
+
1. **\`type\` is the organizational spine.** Every article has exactly one of: \`person | project | place | concept | event | decision | pattern\`. Choose the type BEFORE writing; if you can't pick one, skip the memory instead.
|
|
181
|
+
2. **\`related\` is the navigation surface.** Every article cross-references 2\u20135 other articles via \`[[Article Title]]\` wikilinks in the body AND lists them in the \`related\` output field. Links the reader could follow to learn more.
|
|
182
|
+
3. **Tags are a secondary hint.** 2\u20134 specific tags max. No dates unless the article is an \`event\`. No generic terms ("deploy", "work").
|
|
183
|
+
|
|
184
|
+
## Path convention
|
|
185
|
+
|
|
186
|
+
Path reflects type: \`<type>s/<slug>.md\` where slug is a kebab-case title.
|
|
187
|
+
|
|
188
|
+
- People: \`people/jane-doe.md\`
|
|
189
|
+
- Projects: \`projects/globex-system-v2.md\`
|
|
190
|
+
- Concepts: \`concepts/staging-deployment.md\`
|
|
191
|
+
- Events: \`events/YYYY-MM-DD-<slug>.md\` (date in path)
|
|
192
|
+
- Decisions: \`decisions/<slug>.md\`
|
|
193
|
+
- Patterns: \`patterns/<slug>.md\`
|
|
194
|
+
- Places: \`places/<slug>.md\`
|
|
195
|
+
|
|
196
|
+
## Merge over create
|
|
197
|
+
|
|
198
|
+
If a candidate extends an article in the catalog below, emit an UPDATE at the EXISTING path with the full merged content. Prefer merging over proliferating. Several candidates about the same entity should fold into ONE article.
|
|
199
|
+
|
|
200
|
+
## Writing standards
|
|
201
|
+
|
|
202
|
+
- **Wikipedia-style, flat, factual, encyclopedic.** Write about the entity's role in our work, not a diary of how the memory was learned.
|
|
203
|
+
- **Synthesize, don't quote.** At most 2 short quoted lines per article.
|
|
204
|
+
- **Length: 20\u2013100 lines.**
|
|
205
|
+
- **Every paragraph earns its place.**
|
|
206
|
+
|
|
207
|
+
## Gap Detection
|
|
208
|
+
|
|
209
|
+
Populate a \`gaps\` array: wikilink targets you referenced but for which no article exists yet. Be specific ("Karim Rahmouni \u2014 coach mentioned twice, no people/ article"), not vague ("need more deploy content").
|
|
210
|
+
${s}${l}
|
|
211
|
+
## Output \u2014 valid JSON only, no markdown fencing
|
|
212
|
+
|
|
213
|
+
Reference candidates ONLY by their exact stamp strings (the \`memory:...\` line in each CANDIDATE header). Every candidate must appear in exactly one of \`articles[].promotedFrom\` or \`skipped\`. Do NOT emit frontmatter, owner, access, dates, or sources \u2014 the caller sets those.
|
|
214
|
+
|
|
215
|
+
\`\`\`
|
|
216
|
+
{
|
|
217
|
+
"articles": [
|
|
218
|
+
{
|
|
219
|
+
"path": "concepts/globex-staging-deploy.md",
|
|
220
|
+
"title": "Globex Staging Deploy",
|
|
221
|
+
"type": "concept",
|
|
222
|
+
"related": ["Globex System V2", "Staging Deployment"],
|
|
223
|
+
"tags": ["globex", "deploy"],
|
|
224
|
+
"content": "Deploys to [[Globex System V2]] staging go through \u2026",
|
|
225
|
+
"promotedFrom": ["memory:coder-agent/project_globex_deploy@2026-07-01T22:14:03.000Z"]
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
"skipped": [
|
|
229
|
+
{ "memory": "memory:cx/feedback_short_replies@2026-06-01T00:00:00.000Z", "reason": "agent-specific style preference, not a cross-agent fact" }
|
|
230
|
+
],
|
|
231
|
+
"gaps": []
|
|
232
|
+
}
|
|
233
|
+
\`\`\`
|
|
234
|
+
|
|
235
|
+
CANDIDATES (${u} memories in ${t.length} clusters):
|
|
236
|
+
|
|
237
|
+
${g}`}var v="memory-promoter",re=["project","reference","feedback"],B="_memory-promotions.json",R="memory:",ne=["user","feedback","project","reference"];function G(n,t){return`${n}/${t.type}_${t.name}`}function oe(n,t){return`${R}${G(n,t)}@${t.updatedAt}`}function x(n){if(!n.startsWith(R))return null;let t=n.slice(R.length),o=t.indexOf("/"),e=t.lastIndexOf("@");if(o<=0||e<=o)return null;let s=t.slice(0,o),i=t.slice(o+1,e),a=t.slice(e+1);if(!a)return null;let l=i.indexOf("_");if(l<=0)return null;let u=i.slice(0,l),g=i.slice(l+1);return!ne.includes(u)||!g?null:{agentId:s,type:u,name:g,updatedAt:a,key:`${s}/${i}`}}function se(n,t){let o=new Set;for(let s of t){let i=x(s);i&&o.add(i.key)}let e=[];for(let s of n??[]){let i=x(s);i&&o.has(i.key)||e.includes(s)||e.push(s)}for(let s of t)e.includes(s)||e.push(s);return e}function ie(n=A(process.cwd(),".agentx")){let t=new C({baseDir:n});if(!J(t.baseDir))return[];let o=[];for(let e of Q(t.baseDir,{withFileTypes:!0}))if(!(!e.isDirectory()||e.name.startsWith("_")||e.name.startsWith(".")))for(let s of t.list(e.name))o.push({agentId:e.name,memory:s});return o}var ae=new Set(["promoted","skipped","proposed","rejected"]);function H(n){let t=A(n,B);if(!J(t))return[];try{let o=JSON.parse(ee(t,"utf-8"));return Array.isArray(o)?o.filter(e=>e&&typeof e.stamp=="string"&&ae.has(e.decision)):[]}catch{return[]}}function N(n,t){let o=[...H(n),...t],e=new Map;for(let s of o){let i=x(s.stamp),a=i?.key??s.stamp,l=e.get(a);(!l||i&&(x(l.stamp)?.updatedAt??"")<=i.updatedAt)&&e.set(a,s)}te(A(n,B),JSON.stringify([...e.values()],null,2))}function ce(n){let t=new Map;for(let o of n){let e=`${o.memory.type}_${o.memory.name}`,s=t.get(e)??[];s.push(o),t.set(e,s)}return[...t.values()].map(o=>{let e=[...new Set(o.map(a=>a.agentId))].sort(),s=["project","reference"].includes(o[0].memory.type)?.05:0,i=Math.min(.95,.5+.15*(e.length-1)+s);return{candidates:o,corroboratingAgents:e,confidence:i}})}function W(n,t){let o=n.indexOf("{");if(o===-1)return{error:"no JSON object in response"};let e=0,s=-1,i=!1,a=!1;for(let r=o;r<n.length;r++){let d=n[r];if(a){a=!1;continue}if(d==="\\"){a=!0;continue}if(d==='"'){i=!i;continue}if(!i){if(d==="{")e++;else if(d==="}"&&(e--,e===0)){s=r+1;break}}}if(s===-1)return{error:"unbalanced JSON in response"};let l;try{l=JSON.parse(n.slice(o,s))}catch(r){return{error:`JSON parse error: ${r.message}`}}if(!l||typeof l!="object"||!Array.isArray(l.articles))return{error:"response JSON missing articles array"};let u=[],g=[];for(let r of l.articles){if(!r||typeof r.path!="string"||typeof r.title!="string"||typeof r.content!="string"){u.push("article missing path/title/content \u2014 dropped");continue}let d=Array.isArray(r.promotedFrom)?r.promotedFrom.filter(y=>typeof y=="string"):[],h=d.filter(y=>t.has(y));for(let y of d)t.has(y)||u.push(`article "${r.path}" references unknown stamp "${y}" \u2014 dropped`);if(h.length===0){u.push(`article "${r.path}" has no valid promotedFrom stamps \u2014 dropped`);continue}g.push({path:r.path,title:r.title,type:typeof r.type=="string"?r.type:void 0,related:Array.isArray(r.related)?r.related.filter(y=>typeof y=="string"):void 0,tags:Array.isArray(r.tags)?r.tags.filter(y=>typeof y=="string"):[],content:r.content,promotedFrom:h})}let p=[];for(let r of Array.isArray(l.skipped)?l.skipped:[]){let d=typeof r?.memory=="string"?r.memory:typeof r?.stamp=="string"?r.stamp:"";if(!t.has(d)){d&&u.push(`skip references unknown stamp "${d}" \u2014 dropped`);continue}p.push({stamp:d,reason:typeof r.reason=="string"?r.reason:""})}let c=Array.isArray(l.gaps)?l.gaps.filter(r=>typeof r=="string"):[];return{articles:g,skipped:p,gaps:c,warnings:u}}function pe(n,t,o,e={}){let s=e.types??re,i=e.now??Date.now(),a=e.max??20,l=new Map,u=p=>{let c=x(p);if(!c)return;let r=l.get(c.key);(!r||c.updatedAt>r)&&l.set(c.key,c.updatedAt)};for(let p of t.articles)for(let c of p.sources??[])u(c);for(let p of o)u(p.stamp);let g=[];for(let{agentId:p,memory:c,stamp:r,occurrences:d,sessions:h}of n){if(!s.includes(c.type)||e.agentFilter&&p!==e.agentFilter)continue;if(e.sinceMs!==void 0){let b=Date.parse(c.updatedAt);if(!Number.isFinite(b)||b<i-e.sinceMs)continue}let y=G(p,c),w=l.get(y);w&&c.updatedAt<=w||g.push({agentId:p,memory:c,key:y,stamp:r??oe(p,c),occurrences:d,...h?.length?{sessions:h}:{}})}return g.sort((p,c)=>(c.occurrences??0)-(p.occurrences??0)||c.memory.updatedAt.localeCompare(p.memory.updatedAt)).slice(0,a)}var le="https://api.anthropic.com/v1/messages",de="2023-06-01";async function U(n,t,o=null){let e=t.fetchImpl??fetch,s=t.timeoutMs??18e4,i=new AbortController,a=setTimeout(()=>i.abort(),s);try{if(t.viaAgent){let d=(t.daemonUrl??"http://127.0.0.1:18800").replace(/\/+$/,""),h=o?`Your previous response failed validation: ${o}
|
|
238
|
+
Re-emit the FULL corrected JSON object \u2014 same structure, fixed issues, valid JSON only.`:n,y=await e(`${d}/task`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({agent:t.viaAgent,message:h,freshSession:o===null,context:{channel:"api",chatId:t.chatId??"memory-promote"}}),signal:i.signal});if(!y.ok){let f=await y.text().catch(()=>"");throw new Error(`agent /task ${y.status}: ${f.slice(0,500)}`)}let w=await y.json();if(w.error)throw new Error(`agent reported error: ${w.error}`);let b=String(w.content??"").trim();if(!b)throw new Error("agent returned empty content");return b}if(!t.model)throw new Error("callPromotionLlm: viaAgent or model is required");let l=process.env.ANTHROPIC_API_KEY;if(!l)throw new Error("ANTHROPIC_API_KEY not set; use --via <agentId> instead");let u=[{role:"user",content:n}];o&&(u.push({role:"assistant",content:"(previous attempt; will retry with corrections)"}),u.push({role:"user",content:`Your previous response failed validation: ${o}
|
|
239
|
+
Re-emit the FULL corrected JSON object.`}));let g=await e(le,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":l,"anthropic-version":de},body:JSON.stringify({model:t.model,max_tokens:8e3,messages:u}),signal:i.signal});if(!g.ok){let d=await g.text().catch(()=>"");throw new Error(`anthropic API ${g.status}: ${d.slice(0,500)}`)}let p=await g.json(),r=(Array.isArray(p.content)?p.content:[]).filter(d=>d?.type==="text").map(d=>d.text).join(`
|
|
240
|
+
`).trim();if(!r)throw new Error("anthropic API returned no text content");return r}finally{clearTimeout(a)}}async function Me(n={}){let t=n.wikiDir??A(process.cwd(),".agentx","wiki"),o=n.memoryRoot??A(process.cwd(),".agentx"),e=n.log??(()=>{}),s=n.now??Date.now(),i={candidates:[],clusters:[],proposed:[],skipped:[],gaps:[],warnings:[],errors:[],dryRun:!n.commit},a=new S(t,(...m)=>e(m.map(String).join(" "))),l=a.rebuildIndex(),u=H(t),g=[...ie(o),...n.extraCandidates??[]];if(i.candidates=pe(g,l,u,{types:n.types,sinceMs:n.sinceMs,agentFilter:n.agentFilter,now:s,max:n.max}),i.candidates.length===0)return e("no unpromoted memories in window"),i;if(i.clusters=ce(i.candidates),i.dryRun)return i;let p=l.articles.map(m=>({title:m.title,path:m.path,type:m.type})),c=a.getWorldview()??"",r=n.budgetTokens??me,d=i.clusters,h=O(v,d,p,c);for(;D(h)>r&&d.length>1;)d=d.slice(0,-1),h=O(v,d,p,c);if(D(h)>r)return i.errors.push(`judge prompt (~${D(h)} tokens) exceeds the ${r}-token budget even with one cluster`),i;d.length<i.clusters.length&&i.warnings.push(`token budget: judged ${d.length} of ${i.clusters.length} clusters; the rest wait for the next run`);let y=d.flatMap(m=>m.candidates),w=new Set(y.map(m=>m.stamp)),b=n.chatId??`memory-promote-${new Date(s).toISOString().slice(0,10)}`,f=W(await U(h,{...n,chatId:b}),w);if("error"in f&&(e(`parse failed (${f.error}) \u2014 retrying with feedback`),f=W(await U(h,{...n,chatId:b},f.error),w)),"error"in f)return i.errors.push(`LLM response unusable after retry: ${f.error}`),i;i.warnings.push(...f.warnings),i.gaps=f.gaps;let M=new Date(s).toISOString(),P=[],q=new Map(y.map(m=>[m.stamp,m]));for(let m of f.articles){let k=a.readArticle(m.path);if(k&&!a.canWrite(k.meta,v)){i.errors.push(`write denied for "${m.path}" (owned by ${k.meta.owner??"?"})`);continue}let T=ue(m.promotedFrom,q),I={id:_(s,m.path),createdAt:M,status:"pending",article:m,evidence:T,...k?{replaces:{lastUpdated:k.meta.lastUpdated,contentHash:K(k.content)}}:{}};$(t,I),i.proposed.push({id:I.id,path:m.path,title:m.title,type:m.type,stamps:m.promotedFrom,agents:T.agents,occurrences:T.occurrences});for(let z of m.promotedFrom)P.push({stamp:z,decision:"proposed",article:m.path,proposal:I.id,at:M})}for(let m of f.skipped)P.push({stamp:m.stamp,decision:"skipped",reason:m.reason,at:M}),i.skipped.push(m);return P.length&&N(t,P),i}var me=6e4;function D(n){return Math.ceil(n.length/4)}function K(n){return Z("sha256").update(n).digest("hex").slice(0,16)}var ge=600;function ue(n,t){let o=[];for(let e of n){let s=t.get(e);s&&o.push({stamp:e,kind:e.startsWith("review:")?"review":"memory",agentId:s.agentId,type:s.memory.type,name:s.memory.name,description:s.memory.description,...s.occurrences?{occurrences:s.occurrences}:{},...s.sessions?.length?{sessions:s.sessions}:{},...s.memory.author?{author:s.memory.author}:{},...s.memory.taskId?{taskId:s.memory.taskId}:{},updatedAt:s.memory.updatedAt,excerpt:s.memory.body.slice(0,ge)})}return{agents:[...new Set(o.map(e=>e.agentId))].sort(),occurrences:Math.max(1,...o.map(e=>e.occurrences??1)),sources:o}}function Te(n,t,o={}){let e=E(n,t);if(!e)return{ok:!1,error:`no proposal "${t}"`};if(e.status!=="pending")return{ok:!1,error:`proposal "${t}" is already ${e.status}`};let s=o.log??(()=>{}),i=new S(n,(...d)=>s(d.map(String).join(" "))),{article:a}=e,l=i.readArticle(a.path);if(!o.force){if(l&&!e.replaces)return{ok:!1,error:`"${a.path}" was created after this proposal; review it, then approve with --force`};if(l&&e.replaces&&K(l.content)!==e.replaces.contentHash)return{ok:!1,error:`"${a.path}" changed since this proposal; review it, then approve with --force`}}let u=o.now??Date.now(),g=new Date(u).toISOString().slice(0,10);if(!i.writeArticle(a.path,{title:a.title,type:j(a.type)?a.type:void 0,related:a.related,tags:a.tags,owner:v,access:"public",created:l?.meta.created??g,lastUpdated:g,sources:se(l?.meta.sources,a.promotedFrom)},a.content,v))return{ok:!1,error:`write denied for "${a.path}" (owned by ${l?.meta.owner??"?"})`};let c=new Date(u).toISOString();N(n,a.promotedFrom.map(d=>({stamp:d,decision:"promoted",article:a.path,proposal:t,at:c}))),i.rebuildIndex();let r={...e,status:"approved",decidedBy:o.by??"operator",decidedAt:c};return $(n,r),{ok:!0,proposal:r}}function Ie(n,t,o={}){let e=E(n,t);if(!e)return{ok:!1,error:`no proposal "${t}"`};if(e.status!=="pending")return{ok:!1,error:`proposal "${t}" is already ${e.status}`};let s=new Date(o.now??Date.now()).toISOString();N(n,e.article.promotedFrom.map(a=>({stamp:a,decision:"rejected",article:e.article.path,proposal:t,reason:o.reason,at:s})));let i={...e,status:"rejected",decidedBy:o.by??"operator",decidedAt:s,...o.reason?{reason:o.reason}:{}};return $(n,i),{ok:!0,proposal:i}}export{he as a,v as b,re as c,B as d,G as e,oe as f,x as g,se as h,ie as i,H as j,N as k,ce as l,W as m,pe as n,U as o,Me as p,D as q,ue as r,Te as s,Ie as t};
|
|
241
|
+
//# sourceMappingURL=chunk-CTCOWVNL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/wiki/promote.ts","../src/wiki/prompts.ts"],"sourcesContent":["import { createHash } from \"crypto\"\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { AgentMemory, type MemoryRecord, type MemoryType } from \"../agents/agent-memory\"\nimport { WikiStore } from \"./store\"\nimport { buildMemoryPromotePrompt } from \"./prompts\"\nimport { isWikiArticleType, type WikiIndex } from \"./types\"\nimport { newProposalId, readProposal, saveProposal, type PromotionProposal, type ProposalSource } from \"./proposals\"\n\n// --- Memory → wiki promotion ---\n//\n// Finishes the architecture documented in agent-memory.ts: memory is\n// experiential / per-agent; the wiki is authoritative / cross-agent. This\n// module reads per-agent memories, decides (via an LLM judge) which are\n// durable and fleet-relevant, and PROPOSES them as [[wikilinked]] articles\n// for the SHARED wiki store. Nothing reaches the wiki until an operator\n// approves the proposal (proposals.ts): a wrong or planted lesson would\n// otherwise reach every agent overnight.\n//\n// Idempotency has two layers, both keyed on the versioned stamp\n// `memory:<agentId>/<type>_<name>@<updatedAt>`:\n//\n// 1. Promoted memories: the stamp is recorded in the target article's\n// `sources[]` frontmatter (same pattern as getUnabsorbedEntries).\n// 2. Skipped memories: recorded in `.agentx/wiki/_memory-promotions.json`\n// so permanently-irrelevant memories aren't re-judged every night.\n//\n// A bumped `updatedAt` beats both — an edited memory re-enters the\n// pipeline, and mergeSources() replaces the stale stamp for its key so\n// frontmatter stays bounded (one stamp per memory, not per edit).\n\nexport const PROMOTER_OWNER = \"memory-promoter\"\nexport const DEFAULT_PROMOTE_TYPES: MemoryType[] = [\"project\", \"reference\", \"feedback\"]\nexport const PROMOTION_LEDGER_FILE = \"_memory-promotions.json\"\n\nconst STAMP_PREFIX = \"memory:\"\nconst MEMORY_TYPES: MemoryType[] = [\"user\", \"feedback\", \"project\", \"reference\"]\n\nexport interface MemoryCandidate {\n agentId: string\n memory: MemoryRecord\n /** Identity without version: `<agentId>/<type>_<name>` */\n key: string\n /** Identity + version: `memory:<key>@<updatedAt>` */\n stamp: string\n}\n\nexport interface PromotionLedgerEntry {\n stamp: string\n /** proposed: waiting for an operator; rejected: an operator said no. Both\n * keep the memory from being judged again until it changes. */\n decision: \"promoted\" | \"skipped\" | \"proposed\" | \"rejected\"\n /** Article path when promoted or proposed. */\n article?: string\n /** Proposal id when proposed or decided through one. */\n proposal?: string\n /** LLM skip reason. */\n reason?: string\n /** ISO run timestamp. */\n at: string\n}\n\nexport type PromotionLedger = PromotionLedgerEntry[]\n\n// --- Stamps -------------------------------------------------------------\n\nexport function memoryKey(agentId: string, m: Pick<MemoryRecord, \"type\" | \"name\">): string {\n return `${agentId}/${m.type}_${m.name}`\n}\n\nexport function memoryStamp(agentId: string, m: Pick<MemoryRecord, \"type\" | \"name\" | \"updatedAt\">): string {\n return `${STAMP_PREFIX}${memoryKey(agentId, m)}@${m.updatedAt}`\n}\n\n/** Parse a `memory:<agentId>/<type>_<name>@<updatedAt>` source stamp.\n * Returns null for anything else (raw entry ids, malformed stamps) so\n * callers can filter article `sources[]` safely. */\nexport function parseMemoryStamp(s: string): {\n agentId: string\n type: MemoryType\n name: string\n updatedAt: string\n key: string\n} | null {\n if (!s.startsWith(STAMP_PREFIX)) return null\n const rest = s.slice(STAMP_PREFIX.length)\n const slash = rest.indexOf(\"/\")\n const at = rest.lastIndexOf(\"@\")\n if (slash <= 0 || at <= slash) return null\n const agentId = rest.slice(0, slash)\n const typeName = rest.slice(slash + 1, at)\n const updatedAt = rest.slice(at + 1)\n if (!updatedAt) return null\n const underscore = typeName.indexOf(\"_\")\n if (underscore <= 0) return null\n const type = typeName.slice(0, underscore) as MemoryType\n const name = typeName.slice(underscore + 1)\n if (!MEMORY_TYPES.includes(type) || !name) return null\n return { agentId, type, name, updatedAt, key: `${agentId}/${typeName}` }\n}\n\n/** Merge new promotion stamps into an article's existing sources: a new\n * stamp REPLACES any prior stamp sharing its key (one stamp per memory,\n * not one per edit); everything else (raw entry ids, other memories'\n * stamps) is kept. Deduped, order-stable. */\nexport function mergeSources(existing: string[] | undefined, newStamps: string[]): string[] {\n const newKeys = new Set<string>()\n for (const s of newStamps) {\n const parsed = parseMemoryStamp(s)\n if (parsed) newKeys.add(parsed.key)\n }\n const out: string[] = []\n for (const s of existing ?? []) {\n const parsed = parseMemoryStamp(s)\n if (parsed && newKeys.has(parsed.key)) continue // superseded\n if (!out.includes(s)) out.push(s)\n }\n for (const s of newStamps) {\n if (!out.includes(s)) out.push(s)\n }\n return out\n}\n\n// --- Cross-agent memory enumeration --------------------------------------\n\n/** List every agent's memories. AgentMemory has no cross-agent API — we\n * enumerate the agent subdirs of `.agentx/agent-memory/` ourselves.\n * `memoryRoot` is the `.agentx` directory (AgentMemory's baseDir root). */\nexport function listAllAgentMemories(\n memoryRoot: string = resolve(process.cwd(), \".agentx\"),\n): Array<{ agentId: string; memory: MemoryRecord }> {\n const store = new AgentMemory({ baseDir: memoryRoot })\n if (!existsSync(store.baseDir)) return []\n const out: Array<{ agentId: string; memory: MemoryRecord }> = []\n for (const entry of readdirSync(store.baseDir, { withFileTypes: true })) {\n if (!entry.isDirectory() || entry.name.startsWith(\"_\") || entry.name.startsWith(\".\")) continue\n for (const memory of store.list(entry.name)) {\n out.push({ agentId: entry.name, memory })\n }\n }\n return out\n}\n\n// --- Skip ledger ----------------------------------------------------------\n\nconst LEDGER_DECISIONS = new Set([\"promoted\", \"skipped\", \"proposed\", \"rejected\"])\n\nexport function readPromotionLedger(wikiBaseDir: string): PromotionLedger {\n const path = resolve(wikiBaseDir, PROMOTION_LEDGER_FILE)\n if (!existsSync(path)) return []\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf-8\"))\n if (!Array.isArray(parsed)) return []\n return parsed.filter(\n (e): e is PromotionLedgerEntry =>\n e && typeof e.stamp === \"string\" && LEDGER_DECISIONS.has(e.decision),\n )\n } catch {\n // Corrupt ledger → treat as empty. Worst case: skipped memories are\n // re-judged once. Promoted memories stay deduped via article sources.\n return []\n }\n}\n\n/** Append entries and compact: keep only the newest entry per memory key\n * so the ledger stays bounded. */\nexport function appendPromotionLedger(wikiBaseDir: string, entries: PromotionLedgerEntry[]): void {\n const merged = [...readPromotionLedger(wikiBaseDir), ...entries]\n const byKey = new Map<string, PromotionLedgerEntry>()\n for (const e of merged) {\n const parsed = parseMemoryStamp(e.stamp)\n const key = parsed?.key ?? e.stamp\n const prev = byKey.get(key)\n if (!prev || (parsed && (parseMemoryStamp(prev.stamp)?.updatedAt ?? \"\") <= parsed.updatedAt)) {\n byKey.set(key, e)\n }\n }\n writeFileSync(\n resolve(wikiBaseDir, PROMOTION_LEDGER_FILE),\n JSON.stringify([...byKey.values()], null, 2),\n )\n}\n\n// --- Clustering -------------------------------------------------------------\n\nexport interface PromotionCluster {\n /** ≥1 candidate; >1 when the same `<type>_<name>` exists on several agents. */\n candidates: MemoryCandidate[]\n corroboratingAgents: string[]\n /** 0.5 base + 0.15 per extra corroborating agent + 0.05 for\n * project|reference types, capped at 0.95. Advisory only — shown to\n * the LLM judge, never a hard gate (mirrors clusterWorkflowCandidates). */\n confidence: number\n}\n\n/** Group candidates that share `<type>_<name>` across agents. Exact-match\n * clustering only — two agents naming a memory identically is treated as\n * corroboration; fuzzy matching is future work. */\nexport function groupCandidates(cands: MemoryCandidate[]): PromotionCluster[] {\n const byName = new Map<string, MemoryCandidate[]>()\n for (const c of cands) {\n const k = `${c.memory.type}_${c.memory.name}`\n const list = byName.get(k) ?? []\n list.push(c)\n byName.set(k, list)\n }\n return [...byName.values()].map((candidates) => {\n const corroboratingAgents = [...new Set(candidates.map((c) => c.agentId))].sort()\n const typeBoost = [\"project\", \"reference\"].includes(candidates[0].memory.type) ? 0.05 : 0\n const confidence = Math.min(0.95, 0.5 + 0.15 * (corroboratingAgents.length - 1) + typeBoost)\n return { candidates, corroboratingAgents, confidence }\n })\n}\n\n// --- LLM response parsing ----------------------------------------------------\n\nexport interface PromotedArticle {\n path: string\n title: string\n type?: string\n related?: string[]\n tags: string[]\n content: string\n /** Validated stamps of the memories this article was promoted from. */\n promotedFrom: string[]\n}\n\nexport interface PromotionResponse {\n articles: PromotedArticle[]\n skipped: Array<{ stamp: string; reason: string }>\n gaps: string[]\n /** Stamp refs the LLM emitted that weren't in the offered set, plus\n * articles dropped for having zero valid stamps. For the report. */\n warnings: string[]\n}\n\n/** Parse the promotion LLM's reply. Balanced-JSON extraction (adapted from\n * the wiki absorb CLI): find the outermost `{...}`, depth-scan to its\n * close, JSON.parse, then validate. Stamps not present in `offeredStamps`\n * are hallucinations — dropped; an article left with zero valid stamps is\n * dropped entirely (nothing to dedupe against on the next run). Any\n * failure returns `{error}` — the caller must write nothing. */\nexport function parsePromotionResponse(\n text: string,\n offeredStamps: Set<string>,\n): PromotionResponse | { error: string } {\n const start = text.indexOf(\"{\")\n if (start === -1) return { error: \"no JSON object in response\" }\n let depth = 0\n let end = -1\n let inString = false\n let escaped = false\n for (let i = start; i < text.length; i++) {\n const ch = text[i]\n if (escaped) { escaped = false; continue }\n if (ch === \"\\\\\") { escaped = true; continue }\n if (ch === '\"') { inString = !inString; continue }\n if (inString) continue\n if (ch === \"{\") depth++\n else if (ch === \"}\") { depth--; if (depth === 0) { end = i + 1; break } }\n }\n if (end === -1) return { error: \"unbalanced JSON in response\" }\n\n let parsed: any\n try {\n parsed = JSON.parse(text.slice(start, end))\n } catch (e: any) {\n return { error: `JSON parse error: ${e.message}` }\n }\n if (!parsed || typeof parsed !== \"object\" || !Array.isArray(parsed.articles)) {\n return { error: \"response JSON missing articles array\" }\n }\n\n const warnings: string[] = []\n const articles: PromotedArticle[] = []\n for (const a of parsed.articles) {\n if (!a || typeof a.path !== \"string\" || typeof a.title !== \"string\" || typeof a.content !== \"string\") {\n warnings.push(`article missing path/title/content — dropped`)\n continue\n }\n const raw = Array.isArray(a.promotedFrom) ? a.promotedFrom.filter((s: unknown) => typeof s === \"string\") : []\n const promotedFrom = raw.filter((s: string) => offeredStamps.has(s))\n for (const s of raw) {\n if (!offeredStamps.has(s)) warnings.push(`article \"${a.path}\" references unknown stamp \"${s}\" — dropped`)\n }\n if (promotedFrom.length === 0) {\n warnings.push(`article \"${a.path}\" has no valid promotedFrom stamps — dropped`)\n continue\n }\n articles.push({\n path: a.path,\n title: a.title,\n type: typeof a.type === \"string\" ? a.type : undefined,\n related: Array.isArray(a.related) ? a.related.filter((r: unknown) => typeof r === \"string\") : undefined,\n tags: Array.isArray(a.tags) ? a.tags.filter((t: unknown) => typeof t === \"string\") : [],\n content: a.content,\n promotedFrom,\n })\n }\n\n const skipped: Array<{ stamp: string; reason: string }> = []\n for (const s of Array.isArray(parsed.skipped) ? parsed.skipped : []) {\n const stamp = typeof s?.memory === \"string\" ? s.memory : typeof s?.stamp === \"string\" ? s.stamp : \"\"\n if (!offeredStamps.has(stamp)) {\n if (stamp) warnings.push(`skip references unknown stamp \"${stamp}\" — dropped`)\n continue\n }\n skipped.push({ stamp, reason: typeof s.reason === \"string\" ? s.reason : \"\" })\n }\n\n const gaps = Array.isArray(parsed.gaps) ? parsed.gaps.filter((g: unknown) => typeof g === \"string\") : []\n return { articles, skipped, gaps, warnings }\n}\n\n// --- Candidate selection ---------------------------------------------------\n\nexport interface UnpromotedOptions {\n /** Memory types to consider. Default: project, reference, feedback. */\n types?: MemoryType[]\n /** Only memories updated within this window (ms before `now`). */\n sinceMs?: number\n /** Only this agent's memories. */\n agentFilter?: string\n /** Reference clock for `sinceMs` (injectable for tests). Default Date.now(). */\n now?: number\n /** Cap on returned candidates (newest first). Default 20. */\n max?: number\n}\n\n/** Diff all agent memories against what's already been promoted (article\n * `sources[]` stamps across the shared index) or deliberately skipped\n * (ledger). A memory is a candidate iff its key is unseen OR its\n * `updatedAt` is newer than the recorded one — ISO-8601 strings compare\n * lexicographically, so plain `>` is safe. */\nexport function getUnpromotedMemories(\n all: Array<{\n agentId: string\n memory: MemoryRecord\n /** Supplied by non-memory sources so their namespace survives. */\n stamp?: string\n /** Optional rank — more occurrences sort first. */\n occurrences?: number\n /** Example sessions (review findings), kept as proposal evidence. */\n sessions?: string[]\n }>,\n index: WikiIndex,\n ledger: PromotionLedger,\n opts: UnpromotedOptions = {},\n): MemoryCandidate[] {\n const types = opts.types ?? DEFAULT_PROMOTE_TYPES\n const now = opts.now ?? Date.now()\n const max = opts.max ?? 20\n\n // key → newest updatedAt already handled (promoted or skipped)\n const seen = new Map<string, string>()\n const record = (stamp: string) => {\n const parsed = parseMemoryStamp(stamp)\n if (!parsed) return\n const prev = seen.get(parsed.key)\n if (!prev || parsed.updatedAt > prev) seen.set(parsed.key, parsed.updatedAt)\n }\n for (const article of index.articles) {\n for (const s of article.sources ?? []) record(s)\n }\n for (const e of ledger) record(e.stamp)\n\n const candidates: Array<MemoryCandidate & { occurrences?: number; sessions?: string[] }> = []\n for (const { agentId, memory, stamp, occurrences, sessions } of all) {\n if (!types.includes(memory.type)) continue\n if (opts.agentFilter && agentId !== opts.agentFilter) continue\n if (opts.sinceMs !== undefined) {\n const updated = Date.parse(memory.updatedAt)\n if (!Number.isFinite(updated) || updated < now - opts.sinceMs) continue\n }\n const key = memoryKey(agentId, memory)\n const handled = seen.get(key)\n if (handled && memory.updatedAt <= handled) continue\n // A caller-supplied stamp is kept as-is. Re-deriving it would\n // rewrite a `review:` stamp into the `memory:` namespace and lose\n // the provenance the ledger needs to tell the two sources apart.\n candidates.push({\n agentId, memory, key, stamp: stamp ?? memoryStamp(agentId, memory), occurrences,\n ...(sessions?.length ? { sessions } : {}),\n })\n }\n\n // Recurrence first where a source provides it, then recency. Memory\n // candidates carry no count, so their ordering is unchanged.\n return candidates\n .sort((a, b) =>\n (b.occurrences ?? 0) - (a.occurrences ?? 0) ||\n b.memory.updatedAt.localeCompare(a.memory.updatedAt))\n .slice(0, max)\n}\n\n// --- LLM transport -----------------------------------------------------------\n\nconst ANTHROPIC_API = \"https://api.anthropic.com/v1/messages\"\nconst ANTHROPIC_VERSION = \"2023-06-01\"\n\nexport interface PromotionLlmOptions {\n /** Route through the daemon: POST /task to this agent. No API key needed —\n * the agent's own session handles auth (mirrors architectWorkflowViaAgent). */\n viaAgent?: string\n /** Direct Anthropic API model. Requires ANTHROPIC_API_KEY. */\n model?: string\n daemonUrl?: string\n timeoutMs?: number\n fetchImpl?: typeof fetch\n /** Stable chat id so a retry's --resume replay sees the first reply. */\n chatId?: string\n}\n\n/** Run the promotion prompt through an LLM and return the raw reply text.\n * Throws on transport failure — the caller decides whether to retry.\n * `feedback` turns the call into a correction round: the via-agent branch\n * resumes the same session; the direct branch replays the conversation. */\nexport async function callPromotionLlm(\n prompt: string,\n opts: PromotionLlmOptions,\n feedback: string | null = null,\n): Promise<string> {\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 180_000\n const ctrl = new AbortController()\n const timer = setTimeout(() => ctrl.abort(), timeoutMs)\n try {\n if (opts.viaAgent) {\n const daemonUrl = (opts.daemonUrl ?? \"http://127.0.0.1:18800\").replace(/\\/+$/, \"\")\n const message = feedback\n ? `Your previous response failed validation: ${feedback}\\nRe-emit the FULL corrected JSON object — same structure, fixed issues, valid JSON only.`\n : prompt\n const res = await fetchImpl(`${daemonUrl}/task`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n agent: opts.viaAgent,\n message,\n // First call gets a fresh session; the retry resumes it so the\n // agent can fix its own output instead of starting over.\n freshSession: feedback === null,\n context: { channel: \"api\", chatId: opts.chatId ?? \"memory-promote\" },\n }),\n signal: ctrl.signal,\n })\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\")\n throw new Error(`agent /task ${res.status}: ${errBody.slice(0, 500)}`)\n }\n const body: any = await res.json()\n if (body.error) throw new Error(`agent reported error: ${body.error}`)\n const content = String(body.content ?? \"\").trim()\n if (!content) throw new Error(\"agent returned empty content\")\n return content\n }\n\n if (!opts.model) throw new Error(\"callPromotionLlm: viaAgent or model is required\")\n const apiKey = process.env.ANTHROPIC_API_KEY\n if (!apiKey) throw new Error(\"ANTHROPIC_API_KEY not set; use --via <agentId> instead\")\n const messages: Array<{ role: string; content: string }> = [{ role: \"user\", content: prompt }]\n if (feedback) {\n messages.push({ role: \"assistant\", content: \"(previous attempt; will retry with corrections)\" })\n messages.push({ role: \"user\", content: `Your previous response failed validation: ${feedback}\\nRe-emit the FULL corrected JSON object.` })\n }\n const res = await fetchImpl(ANTHROPIC_API, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_VERSION,\n },\n body: JSON.stringify({ model: opts.model, max_tokens: 8000, messages }),\n signal: ctrl.signal,\n })\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\")\n throw new Error(`anthropic API ${res.status}: ${errBody.slice(0, 500)}`)\n }\n const body: any = await res.json()\n const blocks = Array.isArray(body.content) ? body.content : []\n const text = blocks.filter((b: any) => b?.type === \"text\").map((b: any) => b.text).join(\"\\n\").trim()\n if (!text) throw new Error(\"anthropic API returned no text content\")\n return text\n } finally {\n clearTimeout(timer)\n }\n}\n\n// --- Orchestrator ------------------------------------------------------------\n\nexport interface RunPromotionOptions extends PromotionLlmOptions {\n /** Wiki base dir (the shared store). Default `.agentx/wiki`. */\n wikiDir?: string\n /** The `.agentx` dir holding `agent-memory/`. Default `.agentx`. */\n memoryRoot?: string\n sinceMs?: number\n agentFilter?: string\n types?: MemoryType[]\n max?: number\n /** Candidates from somewhere other than agent memory — currently\n * session-monitor reviews. They join the same pool so they inherit\n * the ledger, the dedupe and the judge rather than getting a second\n * pipeline that drifts from this one. */\n extraCandidates?: MemoryCandidate[]\n /** Write proposals + ledger. Default false (dry-run). */\n commit?: boolean\n /** Upper bound on the judge prompt, in estimated tokens. Clusters are\n * dropped from the end (lowest ranked) until it fits; they stay\n * unledgered, so the next run picks them up. Default 60 000. */\n budgetTokens?: number\n log?: (msg: string) => void\n /** Injectable clock (tests). */\n now?: number\n}\n\nexport interface PromotionReport {\n candidates: MemoryCandidate[]\n clusters: PromotionCluster[]\n /** Proposals written this run; nothing reaches the wiki until approved. */\n proposed: Array<{ id: string; path: string; title: string; type?: string; stamps: string[]; agents: string[]; occurrences: number }>\n skipped: Array<{ stamp: string; reason: string }>\n gaps: string[]\n warnings: string[]\n errors: string[]\n dryRun: boolean\n}\n\n/** End-to-end promotion run: enumerate memories → diff against promoted/\n * skipped → cluster → (dry-run stops here) → LLM judge → write articles\n * with merged sources → ledger → rebuild index. Parses the full response\n * before any write — a bad reply writes nothing, and idempotency makes\n * the next scheduled run retry for free. */\nexport async function runPromotion(opts: RunPromotionOptions = {}): Promise<PromotionReport> {\n const wikiDir = opts.wikiDir ?? resolve(process.cwd(), \".agentx\", \"wiki\")\n const memoryRoot = opts.memoryRoot ?? resolve(process.cwd(), \".agentx\")\n const log = opts.log ?? (() => {})\n const now = opts.now ?? Date.now()\n const report: PromotionReport = {\n candidates: [], clusters: [], proposed: [], skipped: [],\n gaps: [], warnings: [], errors: [], dryRun: !opts.commit,\n }\n\n const store = new WikiStore(wikiDir, (...args: unknown[]) => log(args.map(String).join(\" \")))\n const index = store.rebuildIndex()\n const ledger = readPromotionLedger(wikiDir)\n const all = [...listAllAgentMemories(memoryRoot), ...(opts.extraCandidates ?? [])]\n\n report.candidates = getUnpromotedMemories(all, index, ledger, {\n types: opts.types,\n sinceMs: opts.sinceMs,\n agentFilter: opts.agentFilter,\n now,\n max: opts.max,\n })\n if (report.candidates.length === 0) {\n log(\"no unpromoted memories in window\")\n return report\n }\n report.clusters = groupCandidates(report.candidates)\n if (report.dryRun) return report\n\n const articleList = index.articles.map((a) => ({ title: a.title, path: a.path, type: a.type }))\n const worldview = store.getWorldview() ?? \"\"\n const budget = opts.budgetTokens ?? DEFAULT_BUDGET_TOKENS\n let clusters = report.clusters\n let prompt = buildMemoryPromotePrompt(PROMOTER_OWNER, clusters, articleList, worldview)\n while (estimateTokens(prompt) > budget && clusters.length > 1) {\n clusters = clusters.slice(0, -1)\n prompt = buildMemoryPromotePrompt(PROMOTER_OWNER, clusters, articleList, worldview)\n }\n if (estimateTokens(prompt) > budget) {\n report.errors.push(`judge prompt (~${estimateTokens(prompt)} tokens) exceeds the ${budget}-token budget even with one cluster`)\n return report\n }\n if (clusters.length < report.clusters.length) {\n report.warnings.push(`token budget: judged ${clusters.length} of ${report.clusters.length} clusters; the rest wait for the next run`)\n }\n const judged = clusters.flatMap((c) => c.candidates)\n const offered = new Set(judged.map((c) => c.stamp))\n const chatId = opts.chatId ?? `memory-promote-${new Date(now).toISOString().slice(0, 10)}`\n\n let parsed = parsePromotionResponse(await callPromotionLlm(prompt, { ...opts, chatId }), offered)\n if (\"error\" in parsed) {\n log(`parse failed (${parsed.error}) — retrying with feedback`)\n parsed = parsePromotionResponse(await callPromotionLlm(prompt, { ...opts, chatId }, parsed.error), offered)\n }\n if (\"error\" in parsed) {\n report.errors.push(`LLM response unusable after retry: ${parsed.error}`)\n return report\n }\n report.warnings.push(...parsed.warnings)\n report.gaps = parsed.gaps\n\n const at = new Date(now).toISOString()\n const ledgerEntries: PromotionLedgerEntry[] = []\n\n const byStamp = new Map(judged.map((c) => [c.stamp, c]))\n for (const article of parsed.articles) {\n const existing = store.readArticle(article.path)\n if (existing && !store.canWrite(existing.meta, PROMOTER_OWNER)) {\n // Owned by another agent — a human resolves; stamps stay unledgered\n // so the memory is retried after the standoff clears.\n report.errors.push(`write denied for \"${article.path}\" (owned by ${existing.meta.owner ?? \"?\"})`)\n continue\n }\n const evidence = evidenceFor(article.promotedFrom, byStamp)\n const proposal: PromotionProposal = {\n id: newProposalId(now, article.path),\n createdAt: at,\n status: \"pending\",\n article,\n evidence,\n ...(existing ? { replaces: { lastUpdated: existing.meta.lastUpdated, contentHash: contentHash(existing.content) } } : {}),\n }\n saveProposal(wikiDir, proposal)\n report.proposed.push({\n id: proposal.id, path: article.path, title: article.title, type: article.type,\n stamps: article.promotedFrom, agents: evidence.agents, occurrences: evidence.occurrences,\n })\n for (const stamp of article.promotedFrom) {\n ledgerEntries.push({ stamp, decision: \"proposed\", article: article.path, proposal: proposal.id, at })\n }\n }\n\n for (const s of parsed.skipped) {\n ledgerEntries.push({ stamp: s.stamp, decision: \"skipped\", reason: s.reason, at })\n report.skipped.push(s)\n }\n\n if (ledgerEntries.length) appendPromotionLedger(wikiDir, ledgerEntries)\n return report\n}\n\nconst DEFAULT_BUDGET_TOKENS = 60_000\n\n/** Rough token count (4 characters per token) — enough for a budget. */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4)\n}\n\nfunction contentHash(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16)\n}\n\nconst EXCERPT_CHARS = 600\n\n/** What a reviewer needs to judge a proposal: each source memory or\n * review finding, who wrote it and in which task, how often a finding\n * recurred, and some of the sessions it came from. */\nexport function evidenceFor(stamps: string[], byStamp: Map<string, MemoryCandidate>): PromotionProposal[\"evidence\"] {\n const sources: ProposalSource[] = []\n for (const stamp of stamps) {\n const c = byStamp.get(stamp) as (MemoryCandidate & { occurrences?: number; sessions?: string[] }) | undefined\n if (!c) continue\n sources.push({\n stamp,\n kind: stamp.startsWith(\"review:\") ? \"review\" : \"memory\",\n agentId: c.agentId,\n type: c.memory.type,\n name: c.memory.name,\n description: c.memory.description,\n ...(c.occurrences ? { occurrences: c.occurrences } : {}),\n ...(c.sessions?.length ? { sessions: c.sessions } : {}),\n ...(c.memory.author ? { author: c.memory.author } : {}),\n ...(c.memory.taskId ? { taskId: c.memory.taskId } : {}),\n updatedAt: c.memory.updatedAt,\n excerpt: c.memory.body.slice(0, EXCERPT_CHARS),\n })\n }\n return {\n agents: [...new Set(sources.map((s) => s.agentId))].sort(),\n occurrences: Math.max(1, ...sources.map((s) => s.occurrences ?? 1)),\n sources,\n }\n}\n\n// --- Review: approve or reject a proposal ------------------------------------\n\nexport type ProposalDecisionResult =\n | { ok: true; proposal: PromotionProposal }\n | { ok: false; error: string }\n\n/** Write the proposed article into the shared wiki. Refuses when the\n * article changed (or appeared) since the proposal was made, unless\n * `force`, so an approval can't silently overwrite someone's edit. */\nexport function approveProposal(\n wikiDir: string, id: string, opts: { by?: string; force?: boolean; now?: number; log?: (m: string) => void } = {},\n): ProposalDecisionResult {\n const proposal = readProposal(wikiDir, id)\n if (!proposal) return { ok: false, error: `no proposal \"${id}\"` }\n if (proposal.status !== \"pending\") return { ok: false, error: `proposal \"${id}\" is already ${proposal.status}` }\n\n const log = opts.log ?? (() => {})\n const store = new WikiStore(wikiDir, (...args: unknown[]) => log(args.map(String).join(\" \")))\n const { article } = proposal\n const existing = store.readArticle(article.path)\n if (!opts.force) {\n if (existing && !proposal.replaces) {\n return { ok: false, error: `\"${article.path}\" was created after this proposal; review it, then approve with --force` }\n }\n if (existing && proposal.replaces && contentHash(existing.content) !== proposal.replaces.contentHash) {\n return { ok: false, error: `\"${article.path}\" changed since this proposal; review it, then approve with --force` }\n }\n }\n const now = opts.now ?? Date.now()\n const today = new Date(now).toISOString().slice(0, 10)\n const ok = store.writeArticle(\n article.path,\n {\n title: article.title,\n type: isWikiArticleType(article.type) ? article.type : undefined,\n related: article.related,\n tags: article.tags,\n owner: PROMOTER_OWNER,\n access: \"public\",\n created: existing?.meta.created ?? today,\n lastUpdated: today,\n sources: mergeSources(existing?.meta.sources, article.promotedFrom),\n },\n article.content,\n PROMOTER_OWNER,\n )\n if (!ok) return { ok: false, error: `write denied for \"${article.path}\" (owned by ${existing?.meta.owner ?? \"?\"})` }\n\n const at = new Date(now).toISOString()\n appendPromotionLedger(wikiDir, article.promotedFrom.map((stamp) => ({\n stamp, decision: \"promoted\" as const, article: article.path, proposal: id, at,\n })))\n store.rebuildIndex()\n const decided: PromotionProposal = { ...proposal, status: \"approved\", decidedBy: opts.by ?? \"operator\", decidedAt: at }\n saveProposal(wikiDir, decided)\n return { ok: true, proposal: decided }\n}\n\n/** Decline a proposal. Its memories aren't judged again until they change. */\nexport function rejectProposal(\n wikiDir: string, id: string, opts: { by?: string; reason?: string; now?: number } = {},\n): ProposalDecisionResult {\n const proposal = readProposal(wikiDir, id)\n if (!proposal) return { ok: false, error: `no proposal \"${id}\"` }\n if (proposal.status !== \"pending\") return { ok: false, error: `proposal \"${id}\" is already ${proposal.status}` }\n const at = new Date(opts.now ?? Date.now()).toISOString()\n appendPromotionLedger(wikiDir, proposal.article.promotedFrom.map((stamp) => ({\n stamp, decision: \"rejected\" as const, article: proposal.article.path, proposal: id, reason: opts.reason, at,\n })))\n const decided: PromotionProposal = {\n ...proposal, status: \"rejected\", decidedBy: opts.by ?? \"operator\", decidedAt: at,\n ...(opts.reason ? { reason: opts.reason } : {}),\n }\n saveProposal(wikiDir, decided)\n return { ok: true, proposal: decided }\n}\n","import { fieldChecklistMarkdown, tierLadderMarkdown } from \"@/decisions/seats/article-fields\"\nimport type { WikiMode } from \"./hub\"\nimport type { PromotionCluster } from \"./promote\"\n\n/**\n * Farzapedia-faithful absorb prompt.\n *\n * Replaces the legacy three-mode design (flat/graph/unified) with one prompt\n * that mirrors what Karpathy and Farza describe: article `type` is the\n * organizational spine, `[[wikilinks]]` are the navigation surface, tags are\n * a secondary hint. The LLM chooses paths and cross-references; it does not\n * produce a sprawling per-article tag cloud.\n *\n * The mode parameter is kept in the signature for backwards compatibility but\n * all values resolve to the same prompt — retained callers (hub.ts, wiki CLI)\n * still pass a mode but it no longer changes behavior.\n */\nexport function buildAbsorbPrompt(\n _mode: WikiMode,\n agentId: string,\n worldview: string,\n existingArticles: Array<{ title: string; path: string; type?: string; tags?: string[] }>,\n entryTexts: string,\n entryCount: number,\n factsBlock = \"\",\n): string {\n return buildFarzapediaPrompt(agentId, worldview, existingArticles, entryTexts, entryCount, factsBlock)\n}\n\nfunction buildFarzapediaPrompt(\n agentId: string,\n worldview: string,\n existingArticles: Array<{ title: string; path: string; type?: string; tags?: string[] }>,\n entryTexts: string,\n entryCount: number,\n factsBlock = \"\",\n): string {\n const worldviewSection = worldview ? `\\n## Worldview\\n\\n${worldview}\\n` : \"\"\n\n // Group the existing index by type so the LLM sees the structure.\n const byType = new Map<string, typeof existingArticles>()\n for (const a of existingArticles) {\n const t = a.type || \"untyped\"\n const list = byType.get(t) || []\n list.push(a)\n byType.set(t, list)\n }\n const existingList = existingArticles.length === 0 ? \"\" :\n \"\\n## Existing Articles (the current catalog)\\n\\n\" +\n Array.from(byType.entries()).sort()\n .map(([type, arr]) =>\n `**${type}**\\n` +\n arr.sort((x, y) => x.title.localeCompare(y.title))\n .map(a => `- [[${a.title}]] — ${a.path}`)\n .join(\"\\n\")\n ).join(\"\\n\\n\") + \"\\n\"\n\n return `You are compiling a personal wiki for the \"${agentId}\" agent in the Karpathy / Farzapedia pattern.\n\n## The Pattern (read this first — it shapes every decision below)\n\nThe wiki is a compounding knowledge base of interlinked markdown articles that a future LLM will read by walking the graph, NOT by bag-of-words search. Your job is to produce articles that another agent can navigate via \\`type\\` + wikilinks. If the wikilinks don't form a coherent graph, the wiki is useless regardless of how many tags you add.\n\n**Three rules, in priority order:**\n\n1. **\\`type\\` is the organizational spine.** Every article has exactly one of: \\`person | project | place | concept | event | decision | pattern\\`. Choose the type BEFORE writing; if you can't pick one, the article probably isn't needed.\n2. **\\`related\\` is the navigation surface.** Every article cross-references 2–5 other articles via \\`[[Article Title]]\\` wikilinks in the body AND lists them in the \\`related\\` frontmatter field. Links the reader could follow to learn more.\n3. **Tags are a secondary hint, not a retrieval spine.** 2–4 specific tags max. Do NOT tag aggressively. Do NOT tag with dates unless the article is an \\`event\\`. Do NOT tag with generic terms (\"deploy\", \"work\") that will match half the corpus.\n\n## Path convention\n\nPath reflects type: \\`<type>s/<slug>.md\\` where slug is a kebab-case title.\n\n- People: \\`people/jane-doe.md\\`\n- Projects: \\`projects/globex-system-v2.md\\`\n- Concepts: \\`concepts/staging-deployment.md\\`\n- Events: \\`events/YYYY-MM-DD-<slug>.md\\` (date in path)\n- Decisions: \\`decisions/<slug>.md\\`\n- Patterns: \\`patterns/<slug>.md\\`\n- Places: \\`places/<slug>.md\\`\n\n## How to process entries\n\nFor each of the ${entryCount} raw entries below, ask in this order:\n\n1. **Does it extend an existing article?** If a person/project/concept mentioned in the entry already has an article in the catalog, produce an UPDATE with the full merged content. Prefer merging over proliferating.\n2. **Does it deserve a new article?** Only if the subject is a persistent entity (a person, a project, a recurring concept, a specific event/decision) that future queries will need to find. Not every conversation deserves an article.\n3. **Does it belong in an existing \\`event\\` or \\`decision\\`?** Most work entries fold into one of these.\n4. **Can you skip it?** If the entry is small talk, a transient status ping, or already covered elsewhere — skip. The wiki is curated, not exhaustive.\n\n## Writing standards\n\n- **Wikipedia-style, flat, factual, encyclopedic.** This is the agent's knowledge, not a diary. Write about the *role* of the entity in our work, not a product description.\n- **Synthesize, don't quote.** At most 2 short quoted lines per article.\n- **Organize by theme, not chronology.** An article about a person lists what they do, who they work with, and their actual contact details — not a log of every interaction. History goes last, and only where it still bears on the present.\n- **Length: 20–100 lines.** Articles exceeding 100 lines should split into multiple type-specific articles.\n- **Every paragraph earns its place.** Cut narrative filler. If you can remove a sentence without losing a fact, remove it.\n\n## Build in layers\n\nFacts are not equally load-bearing. Write them in this order, and never\nspend length on a lower layer while a higher one is missing:\n\n${tierLadderMarkdown()}\n\nAn article is a building, not a transcript. The common failure is to\nfurnish a slab: a person's page that records an invoice thread in detail\nand never states their role, their organisation, or how to reach them.\nThat article cannot answer \"who is this\" or \"how do I reach them\" — the\ntwo questions it exists for. History is the last layer, and it is the\nonly one you can safely leave thin.\n\n## Identity before history\n\nAn entity article must first establish **what the entity is**, and only then\nwhat happened to it.\n\nBefore the narrative, every entity article carries an **Identity** section\nwith the fields for its type. State a field even when the entries do not\nanswer it, as \\`unknown\\`, so the gap is visible and a later pass can fill it.\nA field silently omitted is indistinguishable from a field that does not\napply.\n\n${fieldChecklistMarkdown()}\n\nFields in **bold** are the ones an article of that type is unusable without.\nEvery article of any type also states what it is, why it matters to our work,\nand which named people, projects or systems it connects to.\n\nExtract identifiers **verbatim** from the entries. A phone number, an\naccount number, a URL or a path is the single most useful thing an article\ncan carry, and it is exactly what summarising discards. Never paraphrase or\nredact one that appears in the source; if a value appears only in part, say\nso rather than dropping it.\n\n## Wikilink discipline\n\n- Use \\`[[Article Title]]\\` **inside the body** every time another tracked entity is referenced.\n- List those same targets in the \\`related\\` frontmatter field.\n- If you reference something that has no article yet (\\`[[New Thing]]\\`), add \"New Thing\" to the output \\`gaps\\` array so the next pass can create it.\n\n## Article frontmatter (exact shape — do not invent fields)\n\n\\`\\`\\`yaml\n---\ntitle: \"Article Title\"\ntype: person | project | place | concept | event | decision | pattern\nrelated: [\"Other Article\", \"Another Article\"]\ntags: [\"2-4-specific-tags\", \"no-dates-unless-event\"]\nowner: ${agentId}\naccess: public | shared | private\ncreated: YYYY-MM-DD\nlast_updated: YYYY-MM-DD\nsources: [\"entry-id-1\", \"entry-id-2\"]\n---\n\\`\\`\\`\n\nAccess guidance: default \\`public\\`; \\`private\\` only for sensitive credentials or agent-specific learnings; \\`shared\\` with specific agent IDs when the article matters only to a subset.\n${worldviewSection}${existingList}${factsBlock}\n## Gap Detection\n\nAfter compiling, populate a \\`gaps\\` array: wikilink targets you referenced but for which no article exists yet. Be specific:\n\n- Good: \"Karim Rahmouni — coach for Tunis Padel club, mentioned twice but no \\`people/\\` article\"\n- Bad: \"We need more content about deployments\"\n\n## Output — valid JSON only, no markdown fencing\n\n\\`\\`\\`\n{\n \"articles\": [\n {\n \"path\": \"projects/globex-system-v2.md\",\n \"title\": \"Globex System V2\",\n \"type\": \"project\",\n \"related\": [\"Alex Rivera\", \"Laravel\", \"Staging Deployment\"],\n \"tags\": [\"globex\", \"laravel\", \"react\"],\n \"content\": \"Globex System V2 is [[Alex Rivera]]'s production Laravel + React app for …\",\n \"sources\": [\"entry-id-1\", \"entry-id-2\"]\n }\n ],\n \"gaps\": [\n \"Karim Rahmouni — Tunis Padel coach, mentioned in entries but no people/ article\"\n ]\n}\n\\`\\`\\`\n\nENTRIES (${entryCount}):\n\n${entryTexts}`\n}\n\n/** Max chars of a memory body shown to the promotion judge. */\nexport const PROMOTE_BODY_LIMIT = 2000\n\n/** Past this catalog size, list titles only (no paths) to bound the prompt. */\nconst PROMOTE_CATALOG_LIMIT = 300\n\n/**\n * Memory→wiki promotion prompt. Mirrors the Farzapedia absorb prompt's\n * skeleton (type spine, path convention, wikilink discipline, gaps) but\n * judges per-agent MEMORIES instead of raw entries: promote only durable,\n * cross-agent-relevant facts into the shared wiki.\n *\n * The LLM does NOT emit frontmatter — the caller sets owner/access/dates/\n * sources itself, which removes a whole class of hallucination. Candidates\n * are referenced only by their exact stamp strings.\n */\nexport function buildMemoryPromotePrompt(\n ownerId: string,\n clusters: PromotionCluster[],\n existingArticles: Array<{ title: string; path: string; type?: string }>,\n worldview: string,\n): string {\n const worldviewSection = worldview ? `\\n## Worldview\\n\\n${worldview}\\n` : \"\"\n\n const byType = new Map<string, typeof existingArticles>()\n for (const a of existingArticles) {\n const t = a.type || \"untyped\"\n const list = byType.get(t) || []\n list.push(a)\n byType.set(t, list)\n }\n const titlesOnly = existingArticles.length > PROMOTE_CATALOG_LIMIT\n const existingList = existingArticles.length === 0 ? \"\" :\n `\\n## Existing Articles (the current catalog${titlesOnly ? `, ${existingArticles.length} articles — titles only` : \"\"})\\n\\n` +\n Array.from(byType.entries()).sort()\n .map(([type, arr]) =>\n `**${type}**\\n` +\n arr.sort((x, y) => x.title.localeCompare(y.title))\n .map(a => titlesOnly ? `- [[${a.title}]]` : `- [[${a.title}]] — ${a.path}`)\n .join(\"\\n\")\n ).join(\"\\n\\n\") + \"\\n\"\n\n const candidateCount = clusters.reduce((n, c) => n + c.candidates.length, 0)\n const candidateBlocks = clusters.map(cluster => {\n const corroboration = cluster.corroboratingAgents.length > 1\n ? `corroborated by: ${cluster.corroboratingAgents.join(\", \")} (${cluster.corroboratingAgents.length} agents)`\n : `single agent: ${cluster.corroboratingAgents[0]}`\n return cluster.candidates.map(c => {\n const body = c.memory.body.length > PROMOTE_BODY_LIMIT\n ? c.memory.body.slice(0, PROMOTE_BODY_LIMIT) + \"\\n[… truncated]\"\n : c.memory.body\n return [\n `--- CANDIDATE ${c.stamp} ---`,\n `type: ${c.memory.type} | agent: ${c.agentId} | ${corroboration} | confidence: ${cluster.confidence.toFixed(2)}`,\n `description: ${c.memory.description}`,\n body,\n `--- END CANDIDATE ---`,\n ].join(\"\\n\")\n }).join(\"\\n\\n\")\n }).join(\"\\n\\n\")\n\n return `You are promoting per-agent experiential memories into the shared, authoritative AgentX wiki (owner: \"${ownerId}\").\n\n## The Job\n\nMemory is \"what one agent learned\"; the wiki is \"what every agent should know.\" For each candidate memory below, decide: promote it into a wiki article, or skip it with a reason.\n\n**The promotion test — apply in order, skip on the first \"no\":**\n\n1. **Durable?** Will this still be true in 3 months? Transient state, one-off task context, and dated status pings fail here.\n2. **Cross-agent?** Would a DIFFERENT agent benefit from knowing this? Facts about shared infrastructure, projects, people, and conventions pass; one agent's private workflow habits fail.\n3. **A fact, not a preference?** Personal style preferences private to one agent's operator relationship fail.\n4. **Never a secret.** Credentials, tokens, keys, and passwords are NEVER promoted — skip with reason \"contains secret\". Pointers to where a secret lives (e.g. \"SSH config is in X\") are fine; the secret itself is not.\n\nCorroboration across agents (shown per candidate) is a confidence signal, not a requirement — a single-agent memory that passes the test above should be promoted.\n\n**Three rules for the articles you write, in priority order:**\n\n1. **\\`type\\` is the organizational spine.** Every article has exactly one of: \\`person | project | place | concept | event | decision | pattern\\`. Choose the type BEFORE writing; if you can't pick one, skip the memory instead.\n2. **\\`related\\` is the navigation surface.** Every article cross-references 2–5 other articles via \\`[[Article Title]]\\` wikilinks in the body AND lists them in the \\`related\\` output field. Links the reader could follow to learn more.\n3. **Tags are a secondary hint.** 2–4 specific tags max. No dates unless the article is an \\`event\\`. No generic terms (\"deploy\", \"work\").\n\n## Path convention\n\nPath reflects type: \\`<type>s/<slug>.md\\` where slug is a kebab-case title.\n\n- People: \\`people/jane-doe.md\\`\n- Projects: \\`projects/globex-system-v2.md\\`\n- Concepts: \\`concepts/staging-deployment.md\\`\n- Events: \\`events/YYYY-MM-DD-<slug>.md\\` (date in path)\n- Decisions: \\`decisions/<slug>.md\\`\n- Patterns: \\`patterns/<slug>.md\\`\n- Places: \\`places/<slug>.md\\`\n\n## Merge over create\n\nIf a candidate extends an article in the catalog below, emit an UPDATE at the EXISTING path with the full merged content. Prefer merging over proliferating. Several candidates about the same entity should fold into ONE article.\n\n## Writing standards\n\n- **Wikipedia-style, flat, factual, encyclopedic.** Write about the entity's role in our work, not a diary of how the memory was learned.\n- **Synthesize, don't quote.** At most 2 short quoted lines per article.\n- **Length: 20–100 lines.**\n- **Every paragraph earns its place.**\n\n## Gap Detection\n\nPopulate a \\`gaps\\` array: wikilink targets you referenced but for which no article exists yet. Be specific (\"Karim Rahmouni — coach mentioned twice, no people/ article\"), not vague (\"need more deploy content\").\n${worldviewSection}${existingList}\n## Output — valid JSON only, no markdown fencing\n\nReference candidates ONLY by their exact stamp strings (the \\`memory:...\\` line in each CANDIDATE header). Every candidate must appear in exactly one of \\`articles[].promotedFrom\\` or \\`skipped\\`. Do NOT emit frontmatter, owner, access, dates, or sources — the caller sets those.\n\n\\`\\`\\`\n{\n \"articles\": [\n {\n \"path\": \"concepts/globex-staging-deploy.md\",\n \"title\": \"Globex Staging Deploy\",\n \"type\": \"concept\",\n \"related\": [\"Globex System V2\", \"Staging Deployment\"],\n \"tags\": [\"globex\", \"deploy\"],\n \"content\": \"Deploys to [[Globex System V2]] staging go through …\",\n \"promotedFrom\": [\"memory:coder-agent/project_globex_deploy@2026-07-01T22:14:03.000Z\"]\n }\n ],\n \"skipped\": [\n { \"memory\": \"memory:cx/feedback_short_replies@2026-06-01T00:00:00.000Z\", \"reason\": \"agent-specific style preference, not a cross-agent fact\" }\n ],\n \"gaps\": []\n}\n\\`\\`\\`\n\nCANDIDATES (${candidateCount} memories in ${clusters.length} clusters):\n\n${candidateBlocks}`\n}\n"],"mappings":"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","names":["createHash","existsSync","readdirSync","readFileSync","writeFileSync","resolve","buildAbsorbPrompt","_mode","agentId","worldview","existingArticles","entryTexts","entryCount","factsBlock","buildFarzapediaPrompt","worldviewSection","byType","a","t","list","existingList","type","arr","x","y","tierLadderMarkdown","fieldChecklistMarkdown","PROMOTE_BODY_LIMIT","PROMOTE_CATALOG_LIMIT","buildMemoryPromotePrompt","ownerId","clusters","titlesOnly","candidateCount","n","candidateBlocks","cluster","corroboration","c","body","PROMOTER_OWNER","DEFAULT_PROMOTE_TYPES","PROMOTION_LEDGER_FILE","STAMP_PREFIX","MEMORY_TYPES","memoryKey","agentId","m","memoryStamp","parseMemoryStamp","s","rest","slash","at","typeName","updatedAt","underscore","type","name","mergeSources","existing","newStamps","newKeys","parsed","out","listAllAgentMemories","memoryRoot","resolve","store","AgentMemory","existsSync","entry","readdirSync","memory","LEDGER_DECISIONS","readPromotionLedger","wikiBaseDir","path","readFileSync","appendPromotionLedger","entries","merged","byKey","e","key","prev","writeFileSync","groupCandidates","cands","byName","c","k","list","candidates","corroboratingAgents","typeBoost","confidence","parsePromotionResponse","text","offeredStamps","start","depth","end","inString","escaped","i","ch","warnings","articles","a","raw","promotedFrom","r","t","skipped","stamp","gaps","g","getUnpromotedMemories","all","index","ledger","opts","types","now","max","seen","record","article","occurrences","sessions","updated","handled","b","ANTHROPIC_API","ANTHROPIC_VERSION","callPromotionLlm","prompt","feedback","fetchImpl","timeoutMs","ctrl","timer","daemonUrl","message","res","errBody","body","content","apiKey","messages","runPromotion","wikiDir","log","report","WikiStore","args","articleList","worldview","budget","DEFAULT_BUDGET_TOKENS","clusters","buildMemoryPromotePrompt","estimateTokens","judged","offered","chatId","ledgerEntries","byStamp","evidence","evidenceFor","proposal","newProposalId","contentHash","saveProposal","createHash","EXCERPT_CHARS","stamps","sources","approveProposal","id","readProposal","today","isWikiArticleType","decided","rejectProposal"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import{existsSync as y,mkdirSync as q,readdirSync as z,readFileSync as B,unlinkSync as S,writeFileSync as w}from"fs";import{resolve as p}from"path";import{createHash as _}from"crypto";import{existsSync as I,mkdirSync as V,readdirSync as O,readFileSync as Y,renameSync as N,unlinkSync as L,writeFileSync as T}from"fs";import{basename as C,dirname as F,resolve as l}from"path";var U=20,g=class extends Error{constructor(n){super(n?"memory changed since you read it (etag mismatch)":"memory does not exist (etag mismatch)");this.currentEtag=n;this.name="MemoryConflictError"}};function f(r){return _("sha256").update(r).digest("hex").slice(0,16)}function b(r,e){if(!e)return;let n=r===null?null:f(r);if(e.ifNoneMatch==="*"&&n!==null)throw new g(n);if(e.ifMatch!==void 0&&e.ifMatch!==n)throw new g(n)}function W(r,e){let n=l(F(r),`.${C(r)}.${process.pid}.${Date.now()}.tmp`);T(n,e),N(n,r)}function D(r){return l(F(r),"_versions",C(r,".md"))}function k(r,e){if(e===null)return;let n=D(r);V(n,{recursive:!0});let i=new Date().toISOString().replace(/[:.]/g,"-"),t=l(n,`${i}.md`);for(let o=1;I(t);o++)t=l(n,`${i}-${o}.md`);T(t,e),X(n)}function X(r){let e=O(r).filter(n=>n.endsWith(".md")).sort();for(let n of e.slice(0,Math.max(0,e.length-U)))try{L(l(r,n))}catch{}}function P(r){let e=D(r);return I(e)?O(e).filter(n=>n.endsWith(".md")).sort().reverse().map(n=>({id:n.slice(0,-3),content:Y(l(e,n),"utf-8")})):[]}var u=["user","feedback","project","reference"],j=class{baseDir;constructor(e={}){let n=e.baseDir??p(process.cwd(),".agentx");this.baseDir=p(n,"agent-memory")}dirFor(e){let n=A(e),i=p(this.baseDir,n);return q(i,{recursive:!0}),i}fileFor(e,n,i){return p(this.dirFor(e),`${n}_${A(i)}.md`)}indexPath(e){return p(this.dirFor(e),"MEMORY.md")}save(e,n){if(!u.includes(e.type))throw new Error(`invalid memory type: ${e.type}`);if(!e.name.trim())throw new Error("memory name is required");if(!e.description.trim())throw new Error("memory description is required");let i=this.fileFor(e.agentId,e.type,e.name),t=m(i);b(t,n);let o=new Date().toISOString(),s=o;if(t){let a=h(t);a&&(s=a.createdAt)}let c={name:A(e.name),type:e.type,description:e.description.trim(),body:e.body.trimEnd(),createdAt:s,updatedAt:o,...e.author?{author:e.author}:{},...e.taskId?{taskId:e.taskId}:{}},d=G(c);return k(i,t),W(i,d),this.rewriteIndex(e.agentId),{...c,etag:f(d)}}append(e,n){let i=this.get(e.agentId,e.name),t=i?`${i.body.trimEnd()}
|
|
2
|
+
|
|
3
|
+
${e.body.trim()}`:e.body;return this.save({...e,type:i?.type??e.type,body:t},n)}get(e,n){for(let i of u){let t=this.fileFor(e,i,n);if(!y(t))continue;let o=m(t)??"",s=h(o);if(s)return{...s,etag:f(o)}}return null}list(e){let n=this.dirFor(e);if(!y(n))return[];let i=[];for(let t of z(n,{withFileTypes:!0})){if(!t.isFile()||!t.name.endsWith(".md")||t.name==="MEMORY.md")continue;let o=m(p(n,t.name))??"",s=h(o);s&&i.push({...s,etag:f(o)})}return i.sort((t,o)=>{let s=u.indexOf(t.type),c=u.indexOf(o.type);return s!==c?s-c:t.name.localeCompare(o.name)})}remove(e,n,i){let t=u.map(o=>this.fileFor(e,o,n)).filter(o=>y(o));if(t.length===0)return!1;i&&b(m(t[0]),i);for(let o of t)k(o,m(o)),S(o);return this.rewriteIndex(e),!0}versions(e,n){return u.flatMap(i=>P(this.fileFor(e,i,n))).sort((i,t)=>t.id.localeCompare(i.id)).map(i=>{let t=h(i.content);return{id:i.id,record:t?{...t,etag:f(i.content)}:null}})}restore(e,n,i,t={}){let o=this.versions(e,n).find(a=>a.id===i);if(!o?.record)return null;let{type:s,description:c,body:d}=o.record;return this.save({agentId:e,type:s,name:n,description:c,body:d,...t})}indexMarkdown(e){let n=this.indexPath(e);return m(n)??""}syncToWorkspace(e,n){if(!n)return;let i=this.indexMarkdown(e).trim(),t=p(n,".agentx-memory.md");i?w(t,i+`
|
|
4
|
+
`):y(t)&&S(t);let o=p(n,"CLAUDE.md"),s="<!-- AGENTX-MEMORY-START \u2014 auto-managed by agentx; edit via `agentx memory` -->",c="<!-- AGENTX-MEMORY-END -->",d=i?`${s}
|
|
5
|
+
${i}
|
|
6
|
+
${c}`:"",a=m(o)??"",x=a.indexOf(s),E=a.indexOf(c),M;if(x>=0&&E>x){let $=a.slice(0,x).replace(/\n+$/,""),v=a.slice(E+c.length).replace(/^\n+/,"");M=d?[$,d,v].filter(Boolean).join(`
|
|
7
|
+
|
|
8
|
+
`)+`
|
|
9
|
+
`:[$,v].filter(Boolean).join(`
|
|
10
|
+
|
|
11
|
+
`)+($||v?`
|
|
12
|
+
`:"")}else if(d)M=a?a.replace(/\n+$/,"")+`
|
|
13
|
+
|
|
14
|
+
`+d+`
|
|
15
|
+
`:d+`
|
|
16
|
+
`;else return;w(o,M)}rewriteIndex(e){let n=this.list(e);if(n.length===0){let o=this.indexPath(e);y(o)&&S(o);return}let i={user:[],feedback:[],project:[],reference:[]};for(let o of n)i[o.type].push(o);let t=[];t.push(`# Memory \u2014 ${e}`,""),t.push("This is the agent's experiential memory: user preferences,"),t.push("feedback, project-specific context, and external references."),t.push("Updated via `agentx memory` CLI or by the agent itself.");for(let o of u){let s=i[o];if(s.length){t.push("",`## ${J(o)}`,"");for(let c of s)t.push(`- **${c.name}** \u2014 ${c.description} _(updated ${c.updatedAt.slice(0,10)})_`)}}t.push(""),w(this.indexPath(e),t.join(`
|
|
17
|
+
`))}};function G(r){return["---",`name: ${r.name}`,`description: ${R(r.description)}`,`type: ${r.type}`,`created: ${r.createdAt}`,`updated: ${r.updatedAt}`,...r.author?[`author: ${R(r.author)}`]:[],...r.taskId?[`task: ${R(r.taskId)}`]:[],"---",""].join(`
|
|
18
|
+
`)+r.body+`
|
|
19
|
+
`}function h(r){if(!r.startsWith(`---
|
|
20
|
+
`))return null;let e=r.indexOf(`
|
|
21
|
+
---
|
|
22
|
+
`,4);if(e<0)return null;let n=r.slice(4,e),i=r.slice(e+5).replace(/^\n+/,"").trimEnd(),t={};for(let o of n.split(`
|
|
23
|
+
`)){let s=o.indexOf(":");if(s<0)continue;let c=o.slice(0,s).trim(),d=H(o.slice(s+1).trim());t[c]=d}return!t.name||!t.type||!t.description||!u.includes(t.type)?null:{name:t.name,type:t.type,description:t.description,body:i,createdAt:t.created??"",updatedAt:t.updated??t.created??"",...t.author?{author:t.author}:{},...t.task?{taskId:t.task}:{}}}function R(r){return/[:#\n]/.test(r)?`"${r.replace(/"/g,'\\"')}"`:r}function H(r){return r.startsWith('"')&&r.endsWith('"')&&r.length>=2?r.slice(1,-1).replace(/\\"/g,'"'):r}function A(r){return r.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"unnamed"}function J(r){return r.charAt(0).toUpperCase()+r.slice(1)}function m(r){try{return B(r,"utf-8")}catch{return null}}export{g as a,j as b};
|
|
24
|
+
//# sourceMappingURL=chunk-DP7RTNDM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agents/agent-memory.ts","../src/agents/memory-versions.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { atomicWrite, checkCondition, etagOf, readVersions, snapshot, type WriteCondition } from \"./memory-versions\"\n\n// --- AgentMemory: Claude-Code-style structured memory per agent ---\n//\n// Four memory kinds, each a separate file with YAML frontmatter + markdown\n// body, gathered under a per-agent root index (MEMORY.md):\n//\n// user/ — \"who this human is, what they do, what they know\"\n// feedback/ — \"rules the user gave me; what to do / avoid\"\n// project/ — \"facts about current work the code can't reveal\"\n// reference/ — \"pointers to external systems (linear board, grafana, …)\"\n//\n// Layout on disk:\n// .agentx/agent-memory/<agentId>/\n// MEMORY.md — one-line hook per memory, auto-maintained\n// user_deep_backend_expertise.md\n// feedback_no_mock_database.md\n// ...\n// _versions/<type>_<name>/<timestamp>.md — prior versions (memory-versions.ts)\n//\n// Philosophy: the wiki is authoritative / cross-agent / documented.\n// Memory is experiential / per-agent / \"what I learned across runs.\"\n// Durable, cross-agent-relevant memories get promoted to shared wiki\n// articles by `agentx wiki promote` (src/wiki/promote.ts) — corroboration\n// across agents boosts confidence but isn't required. This module stays\n// read-only from the promotion pipeline's perspective; authors keep both\n// layers parallel.\n\nexport type MemoryType = \"user\" | \"feedback\" | \"project\" | \"reference\"\n\nexport interface MemoryRecord {\n /** Slug-safe name, unique within an agent. */\n name: string\n type: MemoryType\n /** One-line hook — used as the MEMORY.md index entry. */\n description: string\n /** The body, excluding frontmatter. */\n body: string\n createdAt: string\n updatedAt: string\n /** Who wrote this version: an agent id verified against its running\n * task, \"operator\" (CLI), or \"unverified\" (a caller we couldn't check). */\n author?: string\n /** The running task that wrote it; opens on the Task page. */\n taskId?: string\n /** Content hash of the file as stored. Pass back as `ifMatch` to change\n * it only if nobody else has since. Not stored in the file. */\n etag?: string\n}\n\n/** Who is making a change, recorded in the memory's frontmatter. */\nexport interface MemoryProvenance {\n author?: string\n taskId?: string\n}\n\n/** One entry in a memory's history. */\nexport interface MemoryVersion {\n id: string\n record: MemoryRecord | null\n}\n\nconst TYPES: MemoryType[] = [\"user\", \"feedback\", \"project\", \"reference\"]\n\nexport interface AgentMemoryOptions {\n baseDir?: string\n}\n\nexport class AgentMemory {\n readonly baseDir: string\n\n constructor(opts: AgentMemoryOptions = {}) {\n const root = opts.baseDir ?? resolve(process.cwd(), \".agentx\")\n this.baseDir = resolve(root, \"agent-memory\")\n }\n\n private dirFor(agentId: string): string {\n const safe = slug(agentId)\n const dir = resolve(this.baseDir, safe)\n mkdirSync(dir, { recursive: true })\n return dir\n }\n\n private fileFor(agentId: string, type: MemoryType, name: string): string {\n return resolve(this.dirFor(agentId), `${type}_${slug(name)}.md`)\n }\n\n private indexPath(agentId: string): string {\n return resolve(this.dirFor(agentId), \"MEMORY.md\")\n }\n\n /** Save (create or update) a memory. Keeps createdAt stable across\n * updates; bumps updatedAt. The replaced version goes to history first.\n * With `cond`, throws MemoryConflictError instead of overwriting a\n * version the caller hasn't seen. Always rewrites MEMORY.md. */\n save(args: {\n agentId: string\n type: MemoryType\n name: string\n description: string\n body: string\n } & MemoryProvenance, cond?: WriteCondition): MemoryRecord {\n if (!TYPES.includes(args.type)) throw new Error(`invalid memory type: ${args.type}`)\n if (!args.name.trim()) throw new Error(\"memory name is required\")\n if (!args.description.trim()) throw new Error(\"memory description is required\")\n\n const path = this.fileFor(args.agentId, args.type, args.name)\n const existing = safeRead(path)\n checkCondition(existing, cond)\n const now = new Date().toISOString()\n let createdAt = now\n if (existing) {\n const parsed = parseMemoryFile(existing)\n if (parsed) createdAt = parsed.createdAt\n }\n const record: MemoryRecord = {\n name: slug(args.name),\n type: args.type,\n description: args.description.trim(),\n body: args.body.trimEnd(),\n createdAt,\n updatedAt: now,\n ...(args.author ? { author: args.author } : {}),\n ...(args.taskId ? { taskId: args.taskId } : {}),\n }\n const content = serialize(record)\n snapshot(path, existing)\n atomicWrite(path, content)\n this.rewriteIndex(args.agentId)\n return { ...record, etag: etagOf(content) }\n }\n\n /** Add to the end of an existing memory's body (or create it) in one\n * step, so two appends can't lose each other. */\n append(args: {\n agentId: string\n type: MemoryType\n name: string\n description: string\n body: string\n } & MemoryProvenance, cond?: WriteCondition): MemoryRecord {\n const existing = this.get(args.agentId, args.name)\n const body = existing ? `${existing.body.trimEnd()}\\n\\n${args.body.trim()}` : args.body\n return this.save({ ...args, type: existing?.type ?? args.type, body }, cond)\n }\n\n get(agentId: string, name: string): MemoryRecord | null {\n for (const type of TYPES) {\n const path = this.fileFor(agentId, type, name)\n if (!existsSync(path)) continue\n const raw = safeRead(path) ?? \"\"\n const parsed = parseMemoryFile(raw)\n if (parsed) return { ...parsed, etag: etagOf(raw) }\n }\n return null\n }\n\n /** Every memory for an agent. Sorted by type then name for stable\n * index rendering. */\n list(agentId: string): MemoryRecord[] {\n const dir = this.dirFor(agentId)\n if (!existsSync(dir)) return []\n const out: MemoryRecord[] = []\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\") || entry.name === \"MEMORY.md\") continue\n const raw = safeRead(resolve(dir, entry.name)) ?? \"\"\n const parsed = parseMemoryFile(raw)\n if (parsed) out.push({ ...parsed, etag: etagOf(raw) })\n }\n return out.sort((a, b) => {\n const ta = TYPES.indexOf(a.type)\n const tb = TYPES.indexOf(b.type)\n if (ta !== tb) return ta - tb\n return a.name.localeCompare(b.name)\n })\n }\n\n /** Remove a memory by name. The removed version stays in history, so\n * `restore` can bring it back. With `cond.ifMatch`, throws\n * MemoryConflictError if it changed since the caller read it. Returns\n * true on success, false if none found. */\n remove(agentId: string, name: string, cond?: WriteCondition): boolean {\n const paths = TYPES.map((t) => this.fileFor(agentId, t, name)).filter((p) => existsSync(p))\n if (paths.length === 0) return false\n if (cond) checkCondition(safeRead(paths[0]), cond)\n for (const path of paths) {\n snapshot(path, safeRead(path))\n unlinkSync(path)\n }\n this.rewriteIndex(agentId)\n return true\n }\n\n /** Prior versions of a memory, newest first, including ones kept after\n * it was deleted. */\n versions(agentId: string, name: string): MemoryVersion[] {\n return TYPES\n .flatMap((t) => readVersions(this.fileFor(agentId, t, name)))\n .sort((a, b) => b.id.localeCompare(a.id))\n .map((v) => {\n const parsed = parseMemoryFile(v.content)\n return { id: v.id, record: parsed ? { ...parsed, etag: etagOf(v.content) } : null }\n })\n }\n\n /** Put a prior version back as the current one. The version it replaces\n * goes to history, so a restore can itself be undone. */\n restore(agentId: string, name: string, versionId: string, by: MemoryProvenance = {}): MemoryRecord | null {\n const version = this.versions(agentId, name).find((v) => v.id === versionId)\n if (!version?.record) return null\n const { type, description, body } = version.record\n return this.save({ agentId, type, name, description, body, ...by })\n }\n\n /** The content of MEMORY.md — used at prompt-build time to inline into\n * the agent's system prompt. Returns \"\" when the agent has no\n * memories (so the prompt builder can no-op). */\n indexMarkdown(agentId: string): string {\n const path = this.indexPath(agentId)\n return safeRead(path) ?? \"\"\n }\n\n /** Push the agent's memory into its workspace so Claude Code sessions\n * pick it up automatically. Does two things:\n *\n * 1. Writes `<workspace>/.agentx-memory.md` — full MEMORY.md content,\n * explicit file the skill can `Read` at session start.\n * 2. Merges that same content into `<workspace>/CLAUDE.md` between\n * sentinel comments so Claude Code auto-loads it into the cached\n * system context on every session. Idempotent: subsequent syncs\n * replace the sentinel block.\n *\n * Never touches content outside the sentinel block — if the file\n * doesn't exist, creates it with just the sentinels. Safe to call\n * on any save / delete. */\n syncToWorkspace(agentId: string, workspacePath: string): void {\n if (!workspacePath) return\n const memory = this.indexMarkdown(agentId).trim()\n\n // 1. Explicit file for the `remember` skill to Read if it wants to.\n const explicitPath = resolve(workspacePath, \".agentx-memory.md\")\n if (memory) {\n writeFileSync(explicitPath, memory + \"\\n\")\n } else if (existsSync(explicitPath)) {\n unlinkSync(explicitPath)\n }\n\n // 2. Sentinel-merged into CLAUDE.md for auto-inject.\n const claudePath = resolve(workspacePath, \"CLAUDE.md\")\n const start = \"<!-- AGENTX-MEMORY-START — auto-managed by agentx; edit via `agentx memory` -->\"\n const end = \"<!-- AGENTX-MEMORY-END -->\"\n const block = memory\n ? `${start}\\n${memory}\\n${end}`\n : \"\"\n\n const existing = safeRead(claudePath) ?? \"\"\n const startIdx = existing.indexOf(start)\n const endIdx = existing.indexOf(end)\n\n let next: string\n if (startIdx >= 0 && endIdx > startIdx) {\n // Replace existing sentinel block (or remove it if memory is empty).\n const before = existing.slice(0, startIdx).replace(/\\n+$/, \"\")\n const after = existing.slice(endIdx + end.length).replace(/^\\n+/, \"\")\n next = block\n ? [before, block, after].filter(Boolean).join(\"\\n\\n\") + \"\\n\"\n : [before, after].filter(Boolean).join(\"\\n\\n\") + (before || after ? \"\\n\" : \"\")\n } else if (block) {\n // No prior block — append to the end of CLAUDE.md (creates the file\n // if needed). Keeps any existing content untouched.\n next = existing\n ? existing.replace(/\\n+$/, \"\") + \"\\n\\n\" + block + \"\\n\"\n : block + \"\\n\"\n } else {\n // Empty memory + no prior block → nothing to do.\n return\n }\n writeFileSync(claudePath, next)\n }\n\n /** Regenerate MEMORY.md from the on-disk files. Called automatically\n * after every save / remove; safe to call manually. */\n rewriteIndex(agentId: string): void {\n const entries = this.list(agentId)\n if (entries.length === 0) {\n const p = this.indexPath(agentId)\n if (existsSync(p)) unlinkSync(p)\n return\n }\n const grouped: Record<MemoryType, MemoryRecord[]> = {\n user: [], feedback: [], project: [], reference: [],\n }\n for (const r of entries) grouped[r.type].push(r)\n\n const lines: string[] = []\n lines.push(`# Memory — ${agentId}`, \"\")\n lines.push(\"This is the agent's experiential memory: user preferences,\")\n lines.push(\"feedback, project-specific context, and external references.\")\n lines.push(\"Updated via `agentx memory` CLI or by the agent itself.\")\n for (const type of TYPES) {\n const group = grouped[type]\n if (!group.length) continue\n lines.push(\"\", `## ${titleCase(type)}`, \"\")\n for (const r of group) {\n lines.push(`- **${r.name}** — ${r.description} _(updated ${r.updatedAt.slice(0, 10)})_`)\n }\n }\n lines.push(\"\")\n writeFileSync(this.indexPath(agentId), lines.join(\"\\n\"))\n }\n}\n\n// --- Serialisation helpers ---------------------------------------------\n\nfunction serialize(r: MemoryRecord): string {\n const frontmatter = [\n \"---\",\n `name: ${r.name}`,\n `description: ${escapeYaml(r.description)}`,\n `type: ${r.type}`,\n `created: ${r.createdAt}`,\n `updated: ${r.updatedAt}`,\n ...(r.author ? [`author: ${escapeYaml(r.author)}`] : []),\n ...(r.taskId ? [`task: ${escapeYaml(r.taskId)}`] : []),\n \"---\",\n \"\",\n ].join(\"\\n\")\n return frontmatter + r.body + \"\\n\"\n}\n\n/** Minimal frontmatter parser. Accepts `key: value` lines between `---`\n * markers; body is everything after. No YAML flow syntax — if we need\n * it later, swap for a proper parser. */\nexport function parseMemoryFile(raw: string): MemoryRecord | null {\n if (!raw.startsWith(\"---\\n\")) return null\n const end = raw.indexOf(\"\\n---\\n\", 4)\n if (end < 0) return null\n const header = raw.slice(4, end)\n const body = raw.slice(end + 5).replace(/^\\n+/, \"\").trimEnd()\n const fm: Record<string, string> = {}\n for (const line of header.split(\"\\n\")) {\n const colon = line.indexOf(\":\")\n if (colon < 0) continue\n const key = line.slice(0, colon).trim()\n const val = unescapeYaml(line.slice(colon + 1).trim())\n fm[key] = val\n }\n if (!fm.name || !fm.type || !fm.description) return null\n if (!TYPES.includes(fm.type as MemoryType)) return null\n return {\n name: fm.name,\n type: fm.type as MemoryType,\n description: fm.description,\n body,\n createdAt: fm.created ?? \"\",\n updatedAt: fm.updated ?? fm.created ?? \"\",\n ...(fm.author ? { author: fm.author } : {}),\n ...(fm.task ? { taskId: fm.task } : {}),\n }\n}\n\nfunction escapeYaml(s: string): string {\n if (/[:#\\n]/.test(s)) return `\"${s.replace(/\"/g, \"\\\\\\\"\")}\"`\n return s\n}\n\nfunction unescapeYaml(s: string): string {\n if (s.startsWith(\"\\\"\") && s.endsWith(\"\\\"\") && s.length >= 2) {\n return s.slice(1, -1).replace(/\\\\\"/g, \"\\\"\")\n }\n return s\n}\n\nfunction slug(s: string): string {\n return s.toLowerCase().replace(/[^a-z0-9]+/g, \"_\").replace(/^_+|_+$/g, \"\") || \"unnamed\"\n}\n\nfunction titleCase(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction safeRead(path: string): string | null {\n try { return readFileSync(path, \"utf-8\") } catch { return null }\n}\n","import { createHash } from \"crypto\"\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from \"fs\"\nimport { basename, dirname, resolve } from \"path\"\n\n// --- Version history and write safety for AgentMemory files ---\n//\n// Every overwrite or delete of a memory file first copies the current file\n// into `_versions/<file stem>/<timestamp>.md` next to it, so any change can\n// be rolled back. Writes go through a temp file and a rename, so a reader\n// never sees half a file. An etag (content hash) lets callers make\n// conditional writes: two agents editing the same memory can no longer\n// silently overwrite each other.\n\n/** Old versions kept per memory. The oldest are pruned past this. */\nexport const MAX_VERSIONS = 20\n\nexport class MemoryConflictError extends Error {\n constructor(readonly currentEtag: string | null) {\n super(currentEtag\n ? \"memory changed since you read it (etag mismatch)\"\n : \"memory does not exist (etag mismatch)\")\n this.name = \"MemoryConflictError\"\n }\n}\n\n/** Conditions for a write, mirroring HTTP If-Match / If-None-Match. */\nexport interface WriteCondition {\n /** Write only when the current file has this etag. */\n ifMatch?: string\n /** \"*\": write only when no memory exists yet (create, never overwrite). */\n ifNoneMatch?: \"*\"\n}\n\nexport function etagOf(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16)\n}\n\n/** Throws MemoryConflictError when `current` (file content, or null when\n * absent) doesn't satisfy `cond`. No condition always passes. */\nexport function checkCondition(current: string | null, cond: WriteCondition | undefined): void {\n if (!cond) return\n const etag = current === null ? null : etagOf(current)\n if (cond.ifNoneMatch === \"*\" && etag !== null) throw new MemoryConflictError(etag)\n if (cond.ifMatch !== undefined && cond.ifMatch !== etag) throw new MemoryConflictError(etag)\n}\n\nexport function atomicWrite(path: string, content: string): void {\n const tmp = resolve(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.tmp`)\n writeFileSync(tmp, content)\n renameSync(tmp, path)\n}\n\nfunction versionsDir(memoryFile: string): string {\n return resolve(dirname(memoryFile), \"_versions\", basename(memoryFile, \".md\"))\n}\n\n/** Copy the current file into its history before it is replaced or\n * removed. No-op when the file doesn't exist yet. */\nexport function snapshot(memoryFile: string, current: string | null): void {\n if (current === null) return\n const dir = versionsDir(memoryFile)\n mkdirSync(dir, { recursive: true })\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\")\n let path = resolve(dir, `${stamp}.md`)\n for (let n = 1; existsSync(path); n++) path = resolve(dir, `${stamp}-${n}.md`)\n writeFileSync(path, current)\n prune(dir)\n}\n\nfunction prune(dir: string): void {\n const files = readdirSync(dir).filter((f) => f.endsWith(\".md\")).sort()\n for (const f of files.slice(0, Math.max(0, files.length - MAX_VERSIONS))) {\n try { unlinkSync(resolve(dir, f)) } catch { /* already gone */ }\n }\n}\n\nexport interface StoredVersion {\n /** File stem, e.g. 2026-09-26T07-30-00-123Z. Pass to restore. */\n id: string\n content: string\n}\n\n/** Stored versions of a memory file, newest first. */\nexport function readVersions(memoryFile: string): StoredVersion[] {\n const dir = versionsDir(memoryFile)\n if (!existsSync(dir)) return []\n return readdirSync(dir)\n .filter((f) => f.endsWith(\".md\"))\n .sort()\n .reverse()\n .map((f) => ({ id: f.slice(0, -3), content: readFileSync(resolve(dir, f), \"utf-8\") }))\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","writeFileSync","resolve","createHash","existsSync","mkdirSync","readdirSync","readFileSync","renameSync","unlinkSync","writeFileSync","basename","dirname","resolve","MAX_VERSIONS","MemoryConflictError","currentEtag","etagOf","content","checkCondition","current","cond","etag","atomicWrite","path","tmp","versionsDir","memoryFile","snapshot","dir","stamp","n","prune","files","f","readVersions","TYPES","AgentMemory","opts","root","resolve","agentId","safe","slug","dir","mkdirSync","type","name","args","cond","path","existing","safeRead","checkCondition","now","createdAt","parsed","parseMemoryFile","record","content","serialize","snapshot","atomicWrite","etagOf","body","existsSync","raw","out","entry","readdirSync","a","b","ta","tb","paths","t","p","unlinkSync","readVersions","v","versionId","by","version","description","workspacePath","memory","explicitPath","writeFileSync","claudePath","start","end","block","startIdx","endIdx","next","before","after","entries","grouped","r","lines","group","titleCase","escapeYaml","header","fm","line","colon","key","val","unescapeYaml","s","readFileSync"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{b as Y,c as X,d as G,e as D,f as I,g as M,h as W}from"./chunk-7CVJ4JOE.js";import{
|
|
1
|
+
import{b as Y,c as X,d as G,e as D,f as I,g as M,h as W}from"./chunk-7CVJ4JOE.js";import{a as K}from"./chunk-RY2EULHS.js";import{l as B,o as q}from"./chunk-QJANPP4C.js";import{existsSync as _,readFileSync as re,writeFileSync as oe,appendFileSync as be,mkdirSync as ye}from"fs";import{dirname as ke,resolve as R}from"path";import{fileURLToPath as we}from"url";import{hostname as $e}from"os";import{spawn as Ae}from"child_process";q();import{existsSync as Q,readFileSync as V,writeFileSync as F,copyFileSync as he}from"fs";import{resolve as Z}from"path";function ee(t,e={}){let n=e.configPath||Z(process.cwd(),"agentx.json");if(!Q(n))throw new Error(`agentx.json not found at ${n}`);let a=JSON.parse(V(n,"utf-8")),r=t(a),o=typeof r=="string"?{summary:r}:r,c=JSON.stringify(a,null,2)+`
|
|
2
2
|
`,p=n+".tmp";F(p,c,"utf-8");try{JSON.parse(V(p,"utf-8"))}catch(d){throw new Error(`config did not round-trip cleanly: ${d.message}`)}let i;return e.skipBackup||(i=`${n}.bak.${Date.now()}`,he(n,i)),F(n,c,"utf-8"),e.skipReload||fe().catch(()=>{}),{summary:o.summary,payload:o.payload,backupPath:i}}function te(t,e={}){let n=e.configPath||Z(process.cwd(),"agentx.json");if(Q(n)&&!e.overwrite)throw new Error(`agentx.json already exists at ${n}. Pass overwrite=true to replace it.`);return F(n,JSON.stringify(t,null,2)+`
|
|
3
3
|
`,"utf-8"),{file:n}}async function fe(){try{let e=B().dashboard.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800";await fetch(`${e}/reload`,{method:"POST"}).catch(()=>null)}catch{}}function ne(t,e){let n=t.configExists?t.agentCount===0?"Add your first agent":"Add another agent":"Set up your team",a=`<header class="ax-topbar">
|
|
4
4
|
<div class="ax-topbar__left">
|
|
@@ -737,4 +737,4 @@ tr:hover td { background: var(--bg-hover); }
|
|
|
737
737
|
<div class="meta-line">${U((d.meta.tags||[]).slice(0,6),e)}</div>
|
|
738
738
|
<h3><a href="${p}/article/${encodeURIComponent(d.path)}">${s(d.meta.title)}</a></h3>
|
|
739
739
|
</div>`;return $(`${e} \xB7 ${n}`,C(r),i)}function P(t,e,n){let a=t.rebuildIndex(),r=L(a.articles.map(c=>({title:c.title,path:c.path,tags:c.tags}))),o='<div class="nav-section">';o+=`<div class="nav-section-title">${s(e)}</div>`,o+=`<a class="nav-item${n?"":" active"}" href="/">Home</a>`,o+='<a class="nav-item" href="/lint">Health check</a>',o+="</div>";for(let[c,p]of r){o+='<div class="nav-section">',o+=`<div class="nav-section-title">${J(c)}</div>`;for(let i of p){let d=i.path===n;o+=`<a class="nav-item${d?" active":""}" href="/article/${encodeURIComponent(i.path)}">${s(i.title)}</a>`}o+="</div>"}return o}function Ge(t){let{wikiDir:e,agentFilter:n,peerUrls:a=[],mode:r="graph",pathPrefix:o=""}=t,c=o.replace(/\/+$/,""),p=new K(e,void 0,r),i=a.length>0?new H(a):null;return async(d,g)=>{let x=new URL(d.url||"/","http://localhost"),l=decodeURIComponent(x.pathname),h=c&&l.startsWith(c)?l.slice(c.length)||"/":l,f=null,E=200;try{if(h.startsWith("/remote/")&&i){let y=h.slice(8).split("/article/");if(y.length===2){let[b,v]=y,m=b.indexOf("/"),k=decodeURIComponent(b.slice(0,m)),u=b.slice(m+1),w=await i.getRemoteArticle(k,u,v);if(w){let z=await i.getRemoteAgents(),N=[...p.summary(),...z.map(S=>({...S,agentId:`${S.agentId} @${S.nodeId}`}))],A=`<div class="breadcrumb"><a href="/">Hub</a> / <span class="tag">remote</span> ${s(u)} @ ${s(k)}</div>`;A+=`<h1>${s(w.meta.title)}</h1>`,A+='<dl class="meta">',A+=`<dt>Tags</dt><dd>${(w.meta.tags||[]).map(S=>`<span class="tag">${S}</span>`).join(" ")}</dd>`,A+=`<dt>Owner</dt><dd>${s(w.meta.owner)}</dd>`,A+=`<dt>Source</dt><dd><span class="tag">remote</span> ${s(k)}</dd>`,A+=`<dt>Updated</dt><dd>${w.meta.lastUpdated}</dd>`,A+="</dl>";let T=new Map;A+=O(w.content,T),f=$(w.meta.title,C(N),A)}}}if(!f&&n){let y=p.getAgentWiki(n);if(h==="/"||h===""){let b=y.rebuildIndex(),v=p.getAgentEntries(n),m=`<h1>${s(n)}</h1>`;if(m+='<div class="stats">',m+=`<div class="stat-card"><div class="number">${b.articles.length}</div><div class="label">Articles</div></div>`,m+=`<div class="stat-card"><div class="number">${v.length}</div><div class="label">Entries</div></div>`,m+="</div>",b.articles.length>0){m+="<h2>Articles</h2><table><tr><th>Title</th><th>Type</th><th>Updated</th></tr>";for(let k of b.articles)m+=`<tr><td><a href="/article/${encodeURIComponent(k.path)}">${s(k.title)}</a></td><td><span class="tag">${(k.tags||[]).slice(0,3).join(", ")}</span></td><td>${k.lastUpdated||""}</td></tr>`;m+="</table>"}f=$(n,P(y,n),m)}else if(h.startsWith("/article/")){let b=h.slice(9),v=y.readArticle(b);if(v){let m=y.rebuildIndex(),k=new Map;for(let w of m.articles)k.set(w.title,w.path);let u=`<h1>${s(v.meta.title)}</h1>`;u+='<dl class="meta">',u+=`<dt>Tags</dt><dd>${(v.meta.tags||[]).map(w=>`<span class="tag">${w}</span>`).join(" ")}</dd>`,u+=`<dt>Updated</dt><dd>${v.meta.lastUpdated}</dd>`,v.meta.sources?.length&&(u+=`<dt>Sources</dt><dd>${v.meta.sources.length} entries</dd>`),u+="</dl>",u+=O(v.content,k),f=$(v.meta.title,P(y,n,b),u)}}else if(h==="/lint"){let b=y.lint(),v="<h1>Health Check</h1>";if(b.length===0)v+='<p style="color:green;">No issues.</p>';else{v+="<table><tr><th>Type</th><th>Article</th><th>Issue</th></tr>";for(let m of b)v+=`<tr><td><span class="tag">${m.type}</span></td><td>${s(m.article)}</td><td>${s(m.message)}</td></tr>`;v+="</table>"}f=$("Health Check",P(y,n),v)}else if(h==="/search"){let b=x.searchParams.get("q")||"",v=await y.findRelevantReranked(b,void 0,20),m=`<h1>Search: "${s(b)}"</h1>`;for(let k of v)m+=`<div class="entry-card"><h3><a href="/article/${encodeURIComponent(k.path)}">${s(k.meta.title)}</a></h3><p>${s(k.content.slice(0,200))}...</p></div>`;v.length===0&&(m+="<p>No results.</p>"),f=$(`Search: ${b}`,P(y,n),m)}else if(h.startsWith("/tag/")){let b=decodeURIComponent(h.slice(5)),v=y.findByTags([b],void 0,100),m=`<h1>Tag: <a class="tag" href="#">${s(b)}</a></h1>`;m+=`<p>${v.length} article${v.length===1?"":"s"} tagged <code>${s(b)}</code></p>`;for(let k of v)m+=`<div class="entry-card"><h3><a href="/article/${encodeURIComponent(k.path)}">${s(k.meta.title)}</a></h3><div class="meta-line">${U((k.meta.tags||[]).slice(0,6))}</div></div>`;v.length===0&&(m+="<p>No articles with this tag.</p>"),f=$(`Tag: ${b}`,P(y,n),m)}}else{let y=i?await i.getRemoteAgents():[],b=p.summary(),v=new Map(b.map(u=>[u.agentId,u])),m=[],k=new Set;for(let u of b){let w=y.find(z=>z.agentId===u.agentId);w&&w.totalArticles>u.totalArticles?m.push({...w,totalEntries:Math.max(u.totalEntries,w.totalEntries),unabsorbed:Math.max(u.unabsorbed,w.unabsorbed)}):m.push(u),k.add(u.agentId)}for(let u of y)k.has(u.agentId)||(m.push(u),k.add(u.agentId));if(h==="/"||h==="")f=je(p,m,y);else if(h==="/entries")f=qe(p,m);else if(h==="/search")f=Ke(p,x.searchParams.get("q")||"",m);else if(h.startsWith("/tag/"))f=Ye(p,decodeURIComponent(h.slice(5)),m);else if(h.match(/^\/agent\/[^/]+\/tag\/.+/)){let u=h.match(/^\/agent\/([^/]+)\/tag\/(.+)$/);f=Xe(p,decodeURIComponent(u[1]),decodeURIComponent(u[2]),m)}else if(h.startsWith("/agent/")){let u=h.slice(7),w=u.indexOf("/article/");if(w===-1)f=Le(p,u,m);else{let z=u.slice(0,w),N=u.slice(w+9);if(f=Je(p,z,N,m),!f&&i){let A=y.find(T=>T.agentId===z);if(A){let T=await i.getRemoteArticle(A.peerUrl,z,N);if(T){let S=`<div class="breadcrumb"><a href="/">Hub</a> / <span class="tag">remote @ ${s(A.nodeId)}</span> / ${s(z)}</div>`;S+=`<h1>${s(T.meta.title)}</h1>`,S+='<dl class="meta">',S+=`<dt>Tags</dt><dd>${(T.meta.tags||[]).map(ue=>`<span class="tag">${ue}</span>`).join(" ")}</dd>`,S+=`<dt>Source</dt><dd><span class="tag">remote</span> ${s(A.nodeId)}</dd>`,S+=`<dt>Updated</dt><dd>${T.meta.lastUpdated}</dd>`,S+="</dl>",S+=O(T.content,new Map),f=$(T.meta.title,C(m),S)}}}}}}if(!f){E=404;let y=n?P(p.getAgentWiki(n),n):C(p.summary());f=$("Not Found",y,"<h1>Page not found</h1>")}}catch(y){E=500,f=$("Error","",`<h1>Error</h1><pre>${s(y.message)}</pre>`)}g.writeHead(E,{"Content-Type":"text/html; charset=utf-8"}),g.end(c?Ve(f,c):f)}}function Ve(t,e){t=t.replace(/(\s(?:href|action)=")\/(?!\/)/g,`$1${e}/`),t=t.replace(new RegExp(`(${e})${e}/`,"g"),`${e}/`);let n=`<a href="/admin" style="position:fixed;top:10px;right:14px;z-index:9999;padding:5px 11px;background:rgba(0,0,0,0.55);color:#fff;font:12px/1.2 'IBM Plex Sans',system-ui,sans-serif;text-decoration:none;border-radius:6px;border:1px solid rgba(255,255,255,0.18);backdrop-filter:blur(6px)">\u2190 Dashboard</a>`;return t=t.replace(/<body([^>]*)>/,`<body$1>${n}`),t}function Tt(t,e=4200,n,a=[],r="graph"){let o=Ge({wikiDir:t,agentFilter:n,peerUrls:a,mode:r});He((p,i)=>{o(p,i)}).listen(e)}var Qe="https://ntfy.sh";function zt(t){return{enabled:!!t?.enabled,server:t?.server||Qe,topicSet:!!t?.topic,tokenSet:!!t?.token}}function Rt(t,e){let n={...t??{}};if("enabled"in e&&(n.enabled=!!e.enabled),"server"in e){let a=String(e.server).trim().replace(/\/+$/,"");if(!/^https?:\/\/\S+$/.test(a))throw new Error("ntfy server must be an http(s) URL");n.server=a}if("topic"in e){let a=String(e.topic).trim();if(!a)throw new Error("ntfy topic cannot be empty");n.topic=a}if("token"in e){let a=String(e.token??"").trim();a?n.token=a:delete n.token}if(n.enabled&&!n.topic)throw new Error("set a topic before enabling ntfy");return n}export{ee as a,ie as b,mt as c,ut as d,ht as e,pe as f,yt as g,kt as h,zt as i,Rt as j,Ge as k,Tt as l};
|
|
740
|
-
//# sourceMappingURL=chunk-
|
|
740
|
+
//# sourceMappingURL=chunk-E6EW42EA.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{existsSync as c,mkdirSync as l,readdirSync as g,readFileSync as d,renameSync as u,writeFileSync as m}from"fs";import{resolve as i}from"path";function s(r){return i(r,"_proposals")}function a(r,t){if(!/^[a-z0-9-]+$/.test(t))throw new Error(`invalid proposal id: ${t}`);return i(s(r),`${t}.json`)}function y(r,t){let o=new Date(r).toISOString().slice(0,10),e=t.replace(/\.md$/,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,48);return`${o}-${e||"article"}-${Math.random().toString(36).slice(2,6)}`}function x(r,t){l(s(r),{recursive:!0});let o=a(r,t.id),e=`${o}.${process.pid}.tmp`;m(e,JSON.stringify(t,null,2)+`
|
|
2
|
+
`),u(e,o)}function P(r,t){try{return JSON.parse(d(a(r,t),"utf-8"))}catch{return null}}function v(r,t){let o=s(r);if(!c(o))return[];let e=[];for(let p of g(o).filter(n=>n.endsWith(".json")).sort()){let n=P(r,p.slice(0,-5));n&&(!t||n.status===t)&&e.push(n)}return e}export{s as a,y as b,x as c,P as d,v as e};
|
|
3
|
+
//# sourceMappingURL=chunk-FRHSQVFN.js.map
|