agentix-cli 0.48.0 → 0.49.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.
Files changed (132) hide show
  1. package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +17 -5
  2. package/apps/mac-voice/Sources/AgentXVoice/Config.swift +6 -1
  3. package/apps/mac-voice/Sources/AgentXVoice/main.swift +5 -2
  4. package/dist/{App-EXB4HP3B.js → App-KE4SURSS.js} +2 -2
  5. package/dist/{ChatApp-XMNE3SPH.js → ChatApp-NNHIKXO2.js} +2 -2
  6. package/dist/{agent-ORYXXL5K.js → agent-J7SLJYQ6.js} +2 -2
  7. package/dist/{board-dashboard-2B3HYIXD.js → board-dashboard-LG3ECH7C.js} +5 -5
  8. package/dist/board-dashboard-LG3ECH7C.js.map +1 -0
  9. package/dist/{chunk-GIK7S3OY.js → chunk-3L2FKVR6.js} +7 -7
  10. package/dist/chunk-3L2FKVR6.js.map +1 -0
  11. package/dist/{chunk-26OP3X5B.js → chunk-4NBWF4TL.js} +2 -2
  12. package/dist/{chunk-P7MR53U7.js → chunk-4TITGMPX.js} +2 -2
  13. package/dist/{chunk-XM7ZQW4F.js → chunk-5ALS7SVV.js} +2 -2
  14. package/dist/{chunk-3ZHMIWSV.js → chunk-5XQCGPQJ.js} +2 -2
  15. package/dist/{chunk-FRHSQVFN.js → chunk-AQASR6KR.js} +3 -3
  16. package/dist/chunk-AQASR6KR.js.map +1 -0
  17. package/dist/{chunk-M5R3PWW5.js → chunk-B5XHX44Y.js} +2 -2
  18. package/dist/{chunk-DIV66W3N.js → chunk-DTJKN5VT.js} +10 -10
  19. package/dist/chunk-DTJKN5VT.js.map +1 -0
  20. package/dist/{chunk-OZX7HVHC.js → chunk-FM2GGPSD.js} +172 -165
  21. package/dist/chunk-FM2GGPSD.js.map +1 -0
  22. package/dist/{chunk-4ZX4E7P3.js → chunk-HGT4AKOH.js} +7 -7
  23. package/dist/chunk-HGT4AKOH.js.map +1 -0
  24. package/dist/{chunk-ZUZESPCH.js → chunk-HVQJIROJ.js} +64 -64
  25. package/dist/chunk-HVQJIROJ.js.map +1 -0
  26. package/dist/{chunk-MLLEOFX2.js → chunk-IXUEJBHD.js} +2 -2
  27. package/dist/{chunk-LDILAHMN.js → chunk-JVNFRS26.js} +2 -2
  28. package/dist/{chunk-SL657MIM.js → chunk-MWUJOAAW.js} +2 -2
  29. package/dist/{chunk-DJQA2CWD.js → chunk-N6EWZGIX.js} +6 -6
  30. package/dist/{chunk-DJQA2CWD.js.map → chunk-N6EWZGIX.js.map} +1 -1
  31. package/dist/{chunk-HEQPHUTR.js → chunk-NLZD2QKF.js} +2 -2
  32. package/dist/chunk-OHA3J5ZV.js +243 -0
  33. package/dist/chunk-OHA3J5ZV.js.map +1 -0
  34. package/dist/{chunk-P5JXQRBH.js → chunk-OTW5MPFT.js} +2 -2
  35. package/dist/{chunk-WMI5B36E.js → chunk-OUZ7YVQZ.js} +2 -2
  36. package/dist/{chunk-GAGVFSZM.js → chunk-P2ZYT5D6.js} +2 -2
  37. package/dist/chunk-P2ZYT5D6.js.map +1 -0
  38. package/dist/{chunk-2WPTP5HO.js → chunk-PZCTCCC5.js} +2 -2
  39. package/dist/chunk-Q7C3X3R6.js +61 -0
  40. package/dist/chunk-Q7C3X3R6.js.map +1 -0
  41. package/dist/{chunk-3WLO2UKI.js → chunk-QDE75R4U.js} +2 -2
  42. package/dist/{chunk-2GSEEKQK.js → chunk-RKAJR37K.js} +2 -2
  43. package/dist/{chunk-SFLF64Q3.js → chunk-Z3IP66SL.js} +2 -2
  44. package/dist/cli.js +1 -1
  45. package/dist/client-JZTH46N2.js +2 -0
  46. package/dist/{config-ROQ4VEU3.js → config-R47ITLIY.js} +2 -2
  47. package/dist/config-mutator-T6JGXJ26.js +2 -0
  48. package/dist/context-planner-FJMU5WAT.js +17 -0
  49. package/dist/context-planner-FJMU5WAT.js.map +1 -0
  50. package/dist/doctor-2DMGAWMW.js +2 -0
  51. package/dist/failure-candidates-ARRMFXRY.js +8 -0
  52. package/dist/failure-candidates-ARRMFXRY.js.map +1 -0
  53. package/dist/index.d.ts +14 -0
  54. package/dist/index.js +1 -1
  55. package/dist/program-3CDFUJVE.js +2 -0
  56. package/dist/promote-57YNOVXV.js +2 -0
  57. package/dist/proposals-TX2HBBYV.js +2 -0
  58. package/dist/query-ZT7WXY2F.js +2 -0
  59. package/dist/registry-722GXALE.js +2 -0
  60. package/dist/{request-planner-FAFNFVFX.js → request-planner-PEAYYXAP.js} +2 -2
  61. package/dist/{review-candidates-2XKJ2R6U.js → review-candidates-W7NIHNAI.js} +2 -2
  62. package/dist/{routing-PUPVGJ77.js → routing-7LCO7UJ7.js} +2 -2
  63. package/dist/schedule-tool-ZD3VBUBW.js +2 -0
  64. package/dist/scheduler-GC3FQKXY.js +2 -0
  65. package/dist/{seat-2MW3DEXX.js → seat-HR6QMAR2.js} +2 -2
  66. package/dist/{select-Q3AGLQQX.js → select-4XYRIZ5L.js} +2 -2
  67. package/dist/{surface-usage-FM2ZHGYE.js → surface-usage-V2BX4SBD.js} +2 -2
  68. package/dist/{tool-IVUNSAXY.js → tool-PAQ47HV2.js} +2 -2
  69. package/dist/wiki-EREMPSAE.js +2 -0
  70. package/package.json +1 -1
  71. package/dist/board-dashboard-2B3HYIXD.js.map +0 -1
  72. package/dist/chunk-4ZX4E7P3.js.map +0 -1
  73. package/dist/chunk-DIV66W3N.js.map +0 -1
  74. package/dist/chunk-FRHSQVFN.js.map +0 -1
  75. package/dist/chunk-GAGVFSZM.js.map +0 -1
  76. package/dist/chunk-GIK7S3OY.js.map +0 -1
  77. package/dist/chunk-OZX7HVHC.js.map +0 -1
  78. package/dist/chunk-RYLTCOYE.js +0 -241
  79. package/dist/chunk-RYLTCOYE.js.map +0 -1
  80. package/dist/chunk-WZGABQ72.js +0 -59
  81. package/dist/chunk-WZGABQ72.js.map +0 -1
  82. package/dist/chunk-ZUZESPCH.js.map +0 -1
  83. package/dist/client-6HCY7UMW.js +0 -2
  84. package/dist/config-mutator-ML4BAOFT.js +0 -2
  85. package/dist/context-planner-AQHJMVY2.js +0 -17
  86. package/dist/context-planner-AQHJMVY2.js.map +0 -1
  87. package/dist/doctor-3HWGAPET.js +0 -2
  88. package/dist/program-JLZOHWIU.js +0 -2
  89. package/dist/promote-YGNTKEXI.js +0 -2
  90. package/dist/proposals-MWMEXRDW.js +0 -2
  91. package/dist/query-V76JDSOC.js +0 -2
  92. package/dist/registry-DKN4HEI3.js +0 -2
  93. package/dist/schedule-tool-6J7AHAHH.js +0 -2
  94. package/dist/scheduler-ZHQBSSRH.js +0 -2
  95. package/dist/wiki-FPX2YBKS.js +0 -2
  96. /package/dist/{App-EXB4HP3B.js.map → App-KE4SURSS.js.map} +0 -0
  97. /package/dist/{ChatApp-XMNE3SPH.js.map → ChatApp-NNHIKXO2.js.map} +0 -0
  98. /package/dist/{agent-ORYXXL5K.js.map → agent-J7SLJYQ6.js.map} +0 -0
  99. /package/dist/{chunk-26OP3X5B.js.map → chunk-4NBWF4TL.js.map} +0 -0
  100. /package/dist/{chunk-P7MR53U7.js.map → chunk-4TITGMPX.js.map} +0 -0
  101. /package/dist/{chunk-XM7ZQW4F.js.map → chunk-5ALS7SVV.js.map} +0 -0
  102. /package/dist/{chunk-3ZHMIWSV.js.map → chunk-5XQCGPQJ.js.map} +0 -0
  103. /package/dist/{chunk-M5R3PWW5.js.map → chunk-B5XHX44Y.js.map} +0 -0
  104. /package/dist/{chunk-MLLEOFX2.js.map → chunk-IXUEJBHD.js.map} +0 -0
  105. /package/dist/{chunk-LDILAHMN.js.map → chunk-JVNFRS26.js.map} +0 -0
  106. /package/dist/{chunk-SL657MIM.js.map → chunk-MWUJOAAW.js.map} +0 -0
  107. /package/dist/{chunk-HEQPHUTR.js.map → chunk-NLZD2QKF.js.map} +0 -0
  108. /package/dist/{chunk-P5JXQRBH.js.map → chunk-OTW5MPFT.js.map} +0 -0
  109. /package/dist/{chunk-WMI5B36E.js.map → chunk-OUZ7YVQZ.js.map} +0 -0
  110. /package/dist/{chunk-2WPTP5HO.js.map → chunk-PZCTCCC5.js.map} +0 -0
  111. /package/dist/{chunk-3WLO2UKI.js.map → chunk-QDE75R4U.js.map} +0 -0
  112. /package/dist/{chunk-2GSEEKQK.js.map → chunk-RKAJR37K.js.map} +0 -0
  113. /package/dist/{chunk-SFLF64Q3.js.map → chunk-Z3IP66SL.js.map} +0 -0
  114. /package/dist/{client-6HCY7UMW.js.map → client-JZTH46N2.js.map} +0 -0
  115. /package/dist/{config-ROQ4VEU3.js.map → config-R47ITLIY.js.map} +0 -0
  116. /package/dist/{config-mutator-ML4BAOFT.js.map → config-mutator-T6JGXJ26.js.map} +0 -0
  117. /package/dist/{doctor-3HWGAPET.js.map → doctor-2DMGAWMW.js.map} +0 -0
  118. /package/dist/{program-JLZOHWIU.js.map → program-3CDFUJVE.js.map} +0 -0
  119. /package/dist/{promote-YGNTKEXI.js.map → promote-57YNOVXV.js.map} +0 -0
  120. /package/dist/{proposals-MWMEXRDW.js.map → proposals-TX2HBBYV.js.map} +0 -0
  121. /package/dist/{query-V76JDSOC.js.map → query-ZT7WXY2F.js.map} +0 -0
  122. /package/dist/{registry-DKN4HEI3.js.map → registry-722GXALE.js.map} +0 -0
  123. /package/dist/{request-planner-FAFNFVFX.js.map → request-planner-PEAYYXAP.js.map} +0 -0
  124. /package/dist/{review-candidates-2XKJ2R6U.js.map → review-candidates-W7NIHNAI.js.map} +0 -0
  125. /package/dist/{routing-PUPVGJ77.js.map → routing-7LCO7UJ7.js.map} +0 -0
  126. /package/dist/{schedule-tool-6J7AHAHH.js.map → schedule-tool-ZD3VBUBW.js.map} +0 -0
  127. /package/dist/{scheduler-ZHQBSSRH.js.map → scheduler-GC3FQKXY.js.map} +0 -0
  128. /package/dist/{seat-2MW3DEXX.js.map → seat-HR6QMAR2.js.map} +0 -0
  129. /package/dist/{select-Q3AGLQQX.js.map → select-4XYRIZ5L.js.map} +0 -0
  130. /package/dist/{surface-usage-FM2ZHGYE.js.map → surface-usage-V2BX4SBD.js.map} +0 -0
  131. /package/dist/{tool-IVUNSAXY.js.map → tool-PAQ47HV2.js.map} +0 -0
  132. /package/dist/{wiki-FPX2YBKS.js.map → wiki-EREMPSAE.js.map} +0 -0
@@ -1,241 +0,0 @@
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-LDILAHMN.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-RYLTCOYE.js.map
@@ -1 +0,0 @@
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":"6NAAA,OAAS,cAAAA,MAAkB,SAC3B,OAAS,cAAAC,EAAY,eAAAC,EAAa,gBAAAC,GAAc,iBAAAC,OAAqB,KACrE,OAAS,WAAAC,MAAe,OCejB,SAASC,GACdC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EAAa,GACL,CACR,OAAOC,EAAsBN,EAASC,EAAWC,EAAkBC,EAAYC,EAAYC,CAAU,CACvG,CAEA,SAASC,EACPN,EACAC,EACAC,EACAC,EACAC,EACAC,EAAa,GACL,CACR,IAAME,EAAmBN,EAAY;AAAA;AAAA;AAAA,EAAqBA;AAAA,EAAgB,GAGpEO,EAAS,IAAI,IACnB,QAAWC,KAAKP,EAAkB,CAChC,IAAMQ,EAAID,EAAE,MAAQ,UACdE,EAAOH,EAAO,IAAIE,CAAC,GAAK,CAAC,EAC/BC,EAAK,KAAKF,CAAC,EACXD,EAAO,IAAIE,EAAGC,CAAI,EAEpB,IAAMC,EAAeV,EAAiB,SAAW,EAAI,GACnD;AAAA;AAAA;AAAA,EACA,MAAM,KAAKM,EAAO,QAAQ,CAAC,EAAE,KAAK,EAC/B,IAAI,CAAC,CAACK,EAAMC,CAAG,IACd,KAAKD;AAAA,EACLC,EAAI,KAAK,CAACC,EAAGC,IAAMD,EAAE,MAAM,cAAcC,EAAE,KAAK,CAAC,EAC9C,IAAIP,GAAK,OAAOA,EAAE,kBAAaA,EAAE,MAAM,EACvC,KAAK;AAAA,CAAI,CACd,EAAE,KAAK;AAAA;AAAA,CAAM,EAAI;AAAA,EAErB,MAAO,8CAA8CT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBA0BrCI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBhBa,EAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBnBC,EAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SA0BhBlB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASPO,IAAmBK,IAAeP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WA6BzBD;AAAA;AAAA,EAETD,GACF,CAGO,IAAMgB,EAAqB,IAG5BC,EAAwB,IAYvB,SAASC,EACdC,EACAC,EACArB,EACAD,EACQ,CACR,IAAMM,EAAmBN,EAAY;AAAA;AAAA;AAAA,EAAqBA;AAAA,EAAgB,GAEpEO,EAAS,IAAI,IACnB,QAAWC,KAAKP,EAAkB,CAChC,IAAMQ,EAAID,EAAE,MAAQ,UACdE,EAAOH,EAAO,IAAIE,CAAC,GAAK,CAAC,EAC/BC,EAAK,KAAKF,CAAC,EACXD,EAAO,IAAIE,EAAGC,CAAI,EAEpB,IAAMa,EAAatB,EAAiB,OAASkB,EACvCR,EAAeV,EAAiB,SAAW,EAAI,GACnD;AAAA,2CAA8CsB,EAAa,KAAKtB,EAAiB,qCAAkC;AAAA;AAAA,EACnH,MAAM,KAAKM,EAAO,QAAQ,CAAC,EAAE,KAAK,EAC/B,IAAI,CAAC,CAACK,EAAMC,CAAG,IACd,KAAKD;AAAA,EACLC,EAAI,KAAK,CAACC,EAAGC,IAAMD,EAAE,MAAM,cAAcC,EAAE,KAAK,CAAC,EAC9C,IAAIP,GAAKe,EAAa,OAAOf,EAAE,UAAY,OAAOA,EAAE,kBAAaA,EAAE,MAAM,EACzE,KAAK;AAAA,CAAI,CACd,EAAE,KAAK;AAAA;AAAA,CAAM,EAAI;AAAA,EAEfgB,EAAiBF,EAAS,OAAO,CAACG,EAAG,IAAMA,EAAI,EAAE,WAAW,OAAQ,CAAC,EACrEC,EAAkBJ,EAAS,IAAIK,GAAW,CAC9C,IAAMC,EAAgBD,EAAQ,oBAAoB,OAAS,EACvD,oBAAoBA,EAAQ,oBAAoB,KAAK,IAAI,MAAMA,EAAQ,oBAAoB,iBAC3F,iBAAiBA,EAAQ,oBAAoB,CAAC,IAClD,OAAOA,EAAQ,WAAW,IAAIE,GAAK,CACjC,IAAMC,EAAOD,EAAE,OAAO,KAAK,OAASX,EAChCW,EAAE,OAAO,KAAK,MAAM,EAAGX,CAAkB,EAAI;AAAA,oBAC7CW,EAAE,OAAO,KACb,MAAO,CACL,iBAAiBA,EAAE,YACnB,SAASA,EAAE,OAAO,iBAAiBA,EAAE,aAAaD,mBAA+BD,EAAQ,WAAW,QAAQ,CAAC,IAC7G,gBAAgBE,EAAE,OAAO,cACzBC,EACA,uBACF,EAAE,KAAK;AAAA,CAAI,CACb,CAAC,EAAE,KAAK;AAAA;AAAA,CAAM,CAChB,CAAC,EAAE,KAAK;AAAA;AAAA,CAAM,EAEd,MAAO,yGAAyGT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+ChHf,IAAmBK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAyBPa,iBAA8BF,EAAS;AAAA;AAAA,EAEnDI,GACF,CDzSO,IAAMK,EAAiB,kBACjBC,GAAsC,CAAC,UAAW,YAAa,UAAU,EACzEC,EAAwB,0BAE/BC,EAAe,UACfC,GAA6B,CAAC,OAAQ,WAAY,UAAW,WAAW,EA8BvE,SAASC,EAAUC,EAAiBC,EAAgD,CACzF,MAAO,GAAGD,KAAWC,EAAE,QAAQA,EAAE,MACnC,CAEO,SAASC,GAAYF,EAAiBC,EAA8D,CACzG,MAAO,GAAGJ,IAAeE,EAAUC,EAASC,CAAC,KAAKA,EAAE,WACtD,CAKO,SAASE,EAAiBC,EAMxB,CACP,GAAI,CAACA,EAAE,WAAWP,CAAY,EAAG,OAAO,KACxC,IAAMQ,EAAOD,EAAE,MAAMP,EAAa,MAAM,EAClCS,EAAQD,EAAK,QAAQ,GAAG,EACxBE,EAAKF,EAAK,YAAY,GAAG,EAC/B,GAAIC,GAAS,GAAKC,GAAMD,EAAO,OAAO,KACtC,IAAMN,EAAUK,EAAK,MAAM,EAAGC,CAAK,EAC7BE,EAAWH,EAAK,MAAMC,EAAQ,EAAGC,CAAE,EACnCE,EAAYJ,EAAK,MAAME,EAAK,CAAC,EACnC,GAAI,CAACE,EAAW,OAAO,KACvB,IAAMC,EAAaF,EAAS,QAAQ,GAAG,EACvC,GAAIE,GAAc,EAAG,OAAO,KAC5B,IAAMC,EAAOH,EAAS,MAAM,EAAGE,CAAU,EACnCE,EAAOJ,EAAS,MAAME,EAAa,CAAC,EAC1C,MAAI,CAACZ,GAAa,SAASa,CAAI,GAAK,CAACC,EAAa,KAC3C,CAAE,QAAAZ,EAAS,KAAAW,EAAM,KAAAC,EAAM,UAAAH,EAAW,IAAK,GAAGT,KAAWQ,GAAW,CACzE,CAMO,SAASK,GAAaC,EAAgCC,EAA+B,CAC1F,IAAMC,EAAU,IAAI,IACpB,QAAW,KAAKD,EAAW,CACzB,IAAME,EAASd,EAAiB,CAAC,EAC7Bc,GAAQD,EAAQ,IAAIC,EAAO,GAAG,EAEpC,IAAMC,EAAgB,CAAC,EACvB,QAAW,KAAKJ,GAAY,CAAC,EAAG,CAC9B,IAAMG,EAASd,EAAiB,CAAC,EAC7Bc,GAAUD,EAAQ,IAAIC,EAAO,GAAG,GAC/BC,EAAI,SAAS,CAAC,GAAGA,EAAI,KAAK,CAAC,EAElC,QAAW,KAAKH,EACTG,EAAI,SAAS,CAAC,GAAGA,EAAI,KAAK,CAAC,EAElC,OAAOA,CACT,CAOO,SAASC,GACdC,EAAqBC,EAAQ,QAAQ,IAAI,EAAG,SAAS,EACH,CAClD,IAAMC,EAAQ,IAAIC,EAAY,CAAE,QAASH,CAAW,CAAC,EACrD,GAAI,CAACI,EAAWF,EAAM,OAAO,EAAG,MAAO,CAAC,EACxC,IAAMJ,EAAwD,CAAC,EAC/D,QAAWO,KAASC,EAAYJ,EAAM,QAAS,CAAE,cAAe,EAAK,CAAC,EACpE,GAAI,GAACG,EAAM,YAAY,GAAKA,EAAM,KAAK,WAAW,GAAG,GAAKA,EAAM,KAAK,WAAW,GAAG,GACnF,QAAWE,KAAUL,EAAM,KAAKG,EAAM,IAAI,EACxCP,EAAI,KAAK,CAAE,QAASO,EAAM,KAAM,OAAAE,CAAO,CAAC,EAG5C,OAAOT,CACT,CAIA,IAAMU,GAAmB,IAAI,IAAI,CAAC,WAAY,UAAW,WAAY,UAAU,CAAC,EAEzE,SAASC,EAAoBC,EAAsC,CACxE,IAAMC,EAAOV,EAAQS,EAAalC,CAAqB,EACvD,GAAI,CAAC4B,EAAWO,CAAI,EAAG,MAAO,CAAC,EAC/B,GAAI,CACF,IAAMd,EAAS,KAAK,MAAMe,GAAaD,EAAM,OAAO,CAAC,EACrD,OAAK,MAAM,QAAQd,CAAM,EAClBA,EAAO,OACX,GACC,GAAK,OAAO,EAAE,OAAU,UAAYW,GAAiB,IAAI,EAAE,QAAQ,CACvE,EAJmC,CAAC,CAKtC,MAAE,CAGA,MAAO,CAAC,CACV,CACF,CAIO,SAASK,EAAsBH,EAAqBI,EAAuC,CAChG,IAAMC,EAAS,CAAC,GAAGN,EAAoBC,CAAW,EAAG,GAAGI,CAAO,EACzDE,EAAQ,IAAI,IAClB,QAAWC,KAAKF,EAAQ,CACtB,IAAMlB,EAASd,EAAiBkC,EAAE,KAAK,EACjCC,EAAMrB,GAAQ,KAAOoB,EAAE,MACvBE,EAAOH,EAAM,IAAIE,CAAG,GACtB,CAACC,GAAStB,IAAWd,EAAiBoC,EAAK,KAAK,GAAG,WAAa,KAAOtB,EAAO,YAChFmB,EAAM,IAAIE,EAAKD,CAAC,EAGpBG,GACEnB,EAAQS,EAAalC,CAAqB,EAC1C,KAAK,UAAU,CAAC,GAAGwC,EAAM,OAAO,CAAC,EAAG,KAAM,CAAC,CAC7C,CACF,CAiBO,SAASK,GAAgBC,EAA8C,CAC5E,IAAMC,EAAS,IAAI,IACnB,QAAWC,KAAKF,EAAO,CACrB,IAAMG,EAAI,GAAGD,EAAE,OAAO,QAAQA,EAAE,OAAO,OACjCE,EAAOH,EAAO,IAAIE,CAAC,GAAK,CAAC,EAC/BC,EAAK,KAAKF,CAAC,EACXD,EAAO,IAAIE,EAAGC,CAAI,EAEpB,MAAO,CAAC,GAAGH,EAAO,OAAO,CAAC,EAAE,IAAKI,GAAe,CAC9C,IAAMC,EAAsB,CAAC,GAAG,IAAI,IAAID,EAAW,IAAKH,GAAMA,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,EAC1EK,EAAY,CAAC,UAAW,WAAW,EAAE,SAASF,EAAW,CAAC,EAAE,OAAO,IAAI,EAAI,IAAO,EAClFG,EAAa,KAAK,IAAI,IAAM,GAAM,KAAQF,EAAoB,OAAS,GAAKC,CAAS,EAC3F,MAAO,CAAE,WAAAF,EAAY,oBAAAC,EAAqB,WAAAE,CAAW,CACvD,CAAC,CACH,CA8BO,SAASC,EACdC,EACAC,EACuC,CACvC,IAAMC,EAAQF,EAAK,QAAQ,GAAG,EAC9B,GAAIE,IAAU,GAAI,MAAO,CAAE,MAAO,4BAA6B,EAC/D,IAAIC,EAAQ,EACRC,EAAM,GACNC,EAAW,GACXC,EAAU,GACd,QAASC,EAAIL,EAAOK,EAAIP,EAAK,OAAQO,IAAK,CACxC,IAAMC,EAAKR,EAAKO,CAAC,EACjB,GAAID,EAAS,CAAEA,EAAU,GAAO,SAChC,GAAIE,IAAO,KAAM,CAAEF,EAAU,GAAM,SACnC,GAAIE,IAAO,IAAK,CAAEH,EAAW,CAACA,EAAU,SACxC,GAAI,CAAAA,GACJ,GAAIG,IAAO,IAAKL,YACPK,IAAO,MAAOL,IAAaA,IAAU,GAAG,CAAEC,EAAMG,EAAI,EAAG,QAElE,GAAIH,IAAQ,GAAI,MAAO,CAAE,MAAO,6BAA8B,EAE9D,IAAIvC,EACJ,GAAI,CACFA,EAAS,KAAK,MAAMmC,EAAK,MAAME,EAAOE,CAAG,CAAC,CAC5C,OAASnB,EAAP,CACA,MAAO,CAAE,MAAO,qBAAqBA,EAAE,SAAU,CACnD,CACA,GAAI,CAACpB,GAAU,OAAOA,GAAW,UAAY,CAAC,MAAM,QAAQA,EAAO,QAAQ,EACzE,MAAO,CAAE,MAAO,sCAAuC,EAGzD,IAAM4C,EAAqB,CAAC,EACtBC,EAA8B,CAAC,EACrC,QAAWC,KAAK9C,EAAO,SAAU,CAC/B,GAAI,CAAC8C,GAAK,OAAOA,EAAE,MAAS,UAAY,OAAOA,EAAE,OAAU,UAAY,OAAOA,EAAE,SAAY,SAAU,CACpGF,EAAS,KAAK,mDAA8C,EAC5D,SAEF,IAAMG,EAAM,MAAM,QAAQD,EAAE,YAAY,EAAIA,EAAE,aAAa,OAAQ3D,GAAe,OAAOA,GAAM,QAAQ,EAAI,CAAC,EACtG6D,EAAeD,EAAI,OAAQ5D,GAAciD,EAAc,IAAIjD,CAAC,CAAC,EACnE,QAAWA,KAAK4D,EACTX,EAAc,IAAIjD,CAAC,GAAGyD,EAAS,KAAK,YAAYE,EAAE,mCAAmC3D,mBAAc,EAE1G,GAAI6D,EAAa,SAAW,EAAG,CAC7BJ,EAAS,KAAK,YAAYE,EAAE,uDAAkD,EAC9E,SAEFD,EAAS,KAAK,CACZ,KAAMC,EAAE,KACR,MAAOA,EAAE,MACT,KAAM,OAAOA,EAAE,MAAS,SAAWA,EAAE,KAAO,OAC5C,QAAS,MAAM,QAAQA,EAAE,OAAO,EAAIA,EAAE,QAAQ,OAAQG,GAAe,OAAOA,GAAM,QAAQ,EAAI,OAC9F,KAAM,MAAM,QAAQH,EAAE,IAAI,EAAIA,EAAE,KAAK,OAAQI,GAAe,OAAOA,GAAM,QAAQ,EAAI,CAAC,EACtF,QAASJ,EAAE,QACX,aAAAE,CACF,CAAC,EAGH,IAAMG,EAAoD,CAAC,EAC3D,QAAWhE,KAAK,MAAM,QAAQa,EAAO,OAAO,EAAIA,EAAO,QAAU,CAAC,EAAG,CACnE,IAAMoD,EAAQ,OAAOjE,GAAG,QAAW,SAAWA,EAAE,OAAS,OAAOA,GAAG,OAAU,SAAWA,EAAE,MAAQ,GAClG,GAAI,CAACiD,EAAc,IAAIgB,CAAK,EAAG,CACzBA,GAAOR,EAAS,KAAK,kCAAkCQ,mBAAkB,EAC7E,SAEFD,EAAQ,KAAK,CAAE,MAAAC,EAAO,OAAQ,OAAOjE,EAAE,QAAW,SAAWA,EAAE,OAAS,EAAG,CAAC,EAG9E,IAAMkE,EAAO,MAAM,QAAQrD,EAAO,IAAI,EAAIA,EAAO,KAAK,OAAQsD,GAAe,OAAOA,GAAM,QAAQ,EAAI,CAAC,EACvG,MAAO,CAAE,SAAAT,EAAU,QAAAM,EAAS,KAAAE,EAAM,SAAAT,CAAS,CAC7C,CAsBO,SAASW,GACdC,EAUAC,EACAC,EACAC,EAA0B,CAAC,EACR,CACnB,IAAMC,EAAQD,EAAK,OAASjF,GACtBmF,EAAMF,EAAK,KAAO,KAAK,IAAI,EAC3BG,EAAMH,EAAK,KAAO,GAGlBI,EAAO,IAAI,IACXC,EAAUZ,GAAkB,CAChC,IAAMpD,EAASd,EAAiBkE,CAAK,EACrC,GAAI,CAACpD,EAAQ,OACb,IAAMsB,EAAOyC,EAAK,IAAI/D,EAAO,GAAG,GAC5B,CAACsB,GAAQtB,EAAO,UAAYsB,IAAMyC,EAAK,IAAI/D,EAAO,IAAKA,EAAO,SAAS,CAC7E,EACA,QAAWiE,KAAWR,EAAM,SAC1B,QAAWtE,KAAK8E,EAAQ,SAAW,CAAC,EAAGD,EAAO7E,CAAC,EAEjD,QAAWiC,KAAKsC,EAAQM,EAAO5C,EAAE,KAAK,EAEtC,IAAMU,EAAqF,CAAC,EAC5F,OAAW,CAAE,QAAA/C,EAAS,OAAA2B,EAAQ,MAAA0C,EAAO,YAAAc,EAAa,SAAAC,CAAS,IAAKX,EAAK,CAEnE,GADI,CAACI,EAAM,SAASlD,EAAO,IAAI,GAC3BiD,EAAK,aAAe5E,IAAY4E,EAAK,YAAa,SACtD,GAAIA,EAAK,UAAY,OAAW,CAC9B,IAAMS,EAAU,KAAK,MAAM1D,EAAO,SAAS,EAC3C,GAAI,CAAC,OAAO,SAAS0D,CAAO,GAAKA,EAAUP,EAAMF,EAAK,QAAS,SAEjE,IAAMtC,EAAMvC,EAAUC,EAAS2B,CAAM,EAC/B2D,EAAUN,EAAK,IAAI1C,CAAG,EACxBgD,GAAW3D,EAAO,WAAa2D,GAInCvC,EAAW,KAAK,CACd,QAAA/C,EAAS,OAAA2B,EAAQ,IAAAW,EAAK,MAAO+B,GAASnE,GAAYF,EAAS2B,CAAM,EAAG,YAAAwD,EACpE,GAAIC,GAAU,OAAS,CAAE,SAAAA,CAAS,EAAI,CAAC,CACzC,CAAC,EAKH,OAAOrC,EACJ,KAAK,CAACgB,EAAGwB,KACPA,EAAE,aAAe,IAAMxB,EAAE,aAAe,IACzCwB,EAAE,OAAO,UAAU,cAAcxB,EAAE,OAAO,SAAS,CAAC,EACrD,MAAM,EAAGgB,CAAG,CACjB,CAIA,IAAMS,GAAgB,wCAChBC,GAAoB,aAmB1B,eAAsBC,EACpBC,EACAf,EACAgB,EAA0B,KACT,CACjB,IAAMC,EAAYjB,EAAK,WAAa,MAC9BkB,EAAYlB,EAAK,WAAa,KAC9BmB,EAAO,IAAI,gBACXC,EAAQ,WAAW,IAAMD,EAAK,MAAM,EAAGD,CAAS,EACtD,GAAI,CACF,GAAIlB,EAAK,SAAU,CACjB,IAAMqB,GAAarB,EAAK,WAAa,0BAA0B,QAAQ,OAAQ,EAAE,EAC3EsB,EAAUN,EACZ,6CAA6CA;AAAA,8FAC7CD,EACEQ,EAAM,MAAMN,EAAU,GAAGI,SAAkB,CAC/C,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU,CACnB,MAAOrB,EAAK,SACZ,QAAAsB,EAGA,aAAcN,IAAa,KAC3B,QAAS,CAAE,QAAS,MAAO,OAAQhB,EAAK,QAAU,gBAAiB,CACrE,CAAC,EACD,OAAQmB,EAAK,MACf,CAAC,EACD,GAAI,CAACI,EAAI,GAAI,CACX,IAAMC,EAAU,MAAMD,EAAI,KAAK,EAAE,MAAM,IAAM,EAAE,EAC/C,MAAM,IAAI,MAAM,eAAeA,EAAI,WAAWC,EAAQ,MAAM,EAAG,GAAG,GAAG,EAEvE,IAAMC,EAAY,MAAMF,EAAI,KAAK,EACjC,GAAIE,EAAK,MAAO,MAAM,IAAI,MAAM,yBAAyBA,EAAK,OAAO,EACrE,IAAMC,EAAU,OAAOD,EAAK,SAAW,EAAE,EAAE,KAAK,EAChD,GAAI,CAACC,EAAS,MAAM,IAAI,MAAM,8BAA8B,EAC5D,OAAOA,EAGT,GAAI,CAAC1B,EAAK,MAAO,MAAM,IAAI,MAAM,iDAAiD,EAClF,IAAM2B,EAAS,QAAQ,IAAI,kBAC3B,GAAI,CAACA,EAAQ,MAAM,IAAI,MAAM,wDAAwD,EACrF,IAAMC,EAAqD,CAAC,CAAE,KAAM,OAAQ,QAASb,CAAO,CAAC,EACzFC,IACFY,EAAS,KAAK,CAAE,KAAM,YAAa,QAAS,iDAAkD,CAAC,EAC/FA,EAAS,KAAK,CAAE,KAAM,OAAQ,QAAS,6CAA6CZ;AAAA,wCAAoD,CAAC,GAE3I,IAAMO,EAAM,MAAMN,EAAUL,GAAe,CACzC,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,YAAae,EACb,oBAAqBd,EACvB,EACA,KAAM,KAAK,UAAU,CAAE,MAAOb,EAAK,MAAO,WAAY,IAAM,SAAA4B,CAAS,CAAC,EACtE,OAAQT,EAAK,MACf,CAAC,EACD,GAAI,CAACI,EAAI,GAAI,CACX,IAAMC,EAAU,MAAMD,EAAI,KAAK,EAAE,MAAM,IAAM,EAAE,EAC/C,MAAM,IAAI,MAAM,iBAAiBA,EAAI,WAAWC,EAAQ,MAAM,EAAG,GAAG,GAAG,EAEzE,IAAMC,EAAY,MAAMF,EAAI,KAAK,EAE3B/C,GADS,MAAM,QAAQiD,EAAK,OAAO,EAAIA,EAAK,QAAU,CAAC,GACzC,OAAQd,GAAWA,GAAG,OAAS,MAAM,EAAE,IAAKA,GAAWA,EAAE,IAAI,EAAE,KAAK;AAAA,CAAI,EAAE,KAAK,EACnG,GAAI,CAACnC,EAAM,MAAM,IAAI,MAAM,wCAAwC,EACnE,OAAOA,CACT,QAAE,CACA,aAAa4C,CAAK,CACpB,CACF,CA8CA,eAAsBS,GAAa7B,EAA4B,CAAC,EAA6B,CAC3F,IAAM8B,EAAU9B,EAAK,SAAWvD,EAAQ,QAAQ,IAAI,EAAG,UAAW,MAAM,EAClED,EAAawD,EAAK,YAAcvD,EAAQ,QAAQ,IAAI,EAAG,SAAS,EAChEsF,EAAM/B,EAAK,MAAQ,IAAM,CAAC,GAC1BE,EAAMF,EAAK,KAAO,KAAK,IAAI,EAC3BgC,EAA0B,CAC9B,WAAY,CAAC,EAAG,SAAU,CAAC,EAAG,SAAU,CAAC,EAAG,QAAS,CAAC,EACtD,KAAM,CAAC,EAAG,SAAU,CAAC,EAAG,OAAQ,CAAC,EAAG,OAAQ,CAAChC,EAAK,MACpD,EAEMtD,EAAQ,IAAIuF,EAAUH,EAAS,IAAII,IAAoBH,EAAIG,EAAK,IAAI,MAAM,EAAE,KAAK,GAAG,CAAC,CAAC,EACtFpC,EAAQpD,EAAM,aAAa,EAC3BqD,EAAS9C,EAAoB6E,CAAO,EACpCjC,EAAM,CAAC,GAAGtD,GAAqBC,CAAU,EAAG,GAAIwD,EAAK,iBAAmB,CAAC,CAAE,EASjF,GAPAgC,EAAO,WAAapC,GAAsBC,EAAKC,EAAOC,EAAQ,CAC5D,MAAOC,EAAK,MACZ,QAASA,EAAK,QACd,YAAaA,EAAK,YAClB,IAAAE,EACA,IAAKF,EAAK,GACZ,CAAC,EACGgC,EAAO,WAAW,SAAW,EAC/B,OAAAD,EAAI,kCAAkC,EAC/BC,EAGT,GADAA,EAAO,SAAWnE,GAAgBmE,EAAO,UAAU,EAC/CA,EAAO,OAAQ,OAAOA,EAE1B,IAAMG,EAAcrC,EAAM,SAAS,IAAKX,IAAO,CAAE,MAAOA,EAAE,MAAO,KAAMA,EAAE,KAAM,KAAMA,EAAE,IAAK,EAAE,EACxFiD,EAAY1F,EAAM,aAAa,GAAK,GACpC2F,EAASrC,EAAK,cAAgBsC,GAChCC,EAAWP,EAAO,SAClBjB,EAASyB,EAAyB1H,EAAgByH,EAAUJ,EAAaC,CAAS,EACtF,KAAOK,EAAe1B,CAAM,EAAIsB,GAAUE,EAAS,OAAS,GAC1DA,EAAWA,EAAS,MAAM,EAAG,EAAE,EAC/BxB,EAASyB,EAAyB1H,EAAgByH,EAAUJ,EAAaC,CAAS,EAEpF,GAAIK,EAAe1B,CAAM,EAAIsB,EAC3B,OAAAL,EAAO,OAAO,KAAK,kBAAkBS,EAAe1B,CAAM,yBAAyBsB,sCAA2C,EACvHL,EAELO,EAAS,OAASP,EAAO,SAAS,QACpCA,EAAO,SAAS,KAAK,wBAAwBO,EAAS,aAAaP,EAAO,SAAS,iDAAiD,EAEtI,IAAMU,EAASH,EAAS,QAASvE,GAAMA,EAAE,UAAU,EAC7C2E,EAAU,IAAI,IAAID,EAAO,IAAK1E,GAAMA,EAAE,KAAK,CAAC,EAC5C4E,EAAS5C,EAAK,QAAU,kBAAkB,IAAI,KAAKE,CAAG,EAAE,YAAY,EAAE,MAAM,EAAG,EAAE,IAEnF7D,EAASkC,EAAuB,MAAMuC,EAAiBC,EAAQ,CAAE,GAAGf,EAAM,OAAA4C,CAAO,CAAC,EAAGD,CAAO,EAKhG,GAJI,UAAWtG,IACb0F,EAAI,iBAAiB1F,EAAO,sCAAiC,EAC7DA,EAASkC,EAAuB,MAAMuC,EAAiBC,EAAQ,CAAE,GAAGf,EAAM,OAAA4C,CAAO,EAAGvG,EAAO,KAAK,EAAGsG,CAAO,GAExG,UAAWtG,EACb,OAAA2F,EAAO,OAAO,KAAK,sCAAsC3F,EAAO,OAAO,EAChE2F,EAETA,EAAO,SAAS,KAAK,GAAG3F,EAAO,QAAQ,EACvC2F,EAAO,KAAO3F,EAAO,KAErB,IAAMV,EAAK,IAAI,KAAKuE,CAAG,EAAE,YAAY,EAC/B2C,EAAwC,CAAC,EAEzCC,EAAU,IAAI,IAAIJ,EAAO,IAAK1E,GAAM,CAACA,EAAE,MAAOA,CAAC,CAAC,CAAC,EACvD,QAAWsC,KAAWjE,EAAO,SAAU,CACrC,IAAMH,EAAWQ,EAAM,YAAY4D,EAAQ,IAAI,EAC/C,GAAIpE,GAAY,CAACQ,EAAM,SAASR,EAAS,KAAMpB,CAAc,EAAG,CAG9DkH,EAAO,OAAO,KAAK,qBAAqB1B,EAAQ,mBAAmBpE,EAAS,KAAK,OAAS,MAAM,EAChG,SAEF,IAAM6G,EAAWC,GAAY1C,EAAQ,aAAcwC,CAAO,EACpDG,EAA8B,CAClC,GAAIC,EAAchD,EAAKI,EAAQ,IAAI,EACnC,UAAW3E,EACX,OAAQ,UACR,QAAA2E,EACA,SAAAyC,EACA,GAAI7G,EAAW,CAAE,SAAU,CAAE,YAAaA,EAAS,KAAK,YAAa,YAAaiH,EAAYjH,EAAS,OAAO,CAAE,CAAE,EAAI,CAAC,CACzH,EACAkH,EAAatB,EAASmB,CAAQ,EAC9BjB,EAAO,SAAS,KAAK,CACnB,GAAIiB,EAAS,GAAI,KAAM3C,EAAQ,KAAM,MAAOA,EAAQ,MAAO,KAAMA,EAAQ,KACzE,OAAQA,EAAQ,aAAc,OAAQyC,EAAS,OAAQ,YAAaA,EAAS,WAC/E,CAAC,EACD,QAAWtD,KAASa,EAAQ,aAC1BuC,EAAc,KAAK,CAAE,MAAApD,EAAO,SAAU,WAAY,QAASa,EAAQ,KAAM,SAAU2C,EAAS,GAAI,GAAAtH,CAAG,CAAC,EAIxG,QAAWH,KAAKa,EAAO,QACrBwG,EAAc,KAAK,CAAE,MAAOrH,EAAE,MAAO,SAAU,UAAW,OAAQA,EAAE,OAAQ,GAAAG,CAAG,CAAC,EAChFqG,EAAO,QAAQ,KAAKxG,CAAC,EAGvB,OAAIqH,EAAc,QAAQxF,EAAsByE,EAASe,CAAa,EAC/Db,CACT,CAEA,IAAMM,GAAwB,IAGvB,SAASG,EAAejE,EAAsB,CACnD,OAAO,KAAK,KAAKA,EAAK,OAAS,CAAC,CAClC,CAEA,SAAS2E,EAAYzB,EAAyB,CAC5C,OAAO2B,EAAW,QAAQ,EAAE,OAAO3B,CAAO,EAAE,OAAO,KAAK,EAAE,MAAM,EAAG,EAAE,CACvE,CAEA,IAAM4B,GAAgB,IAKf,SAASN,GAAYO,EAAkBT,EAAsE,CAClH,IAAMU,EAA4B,CAAC,EACnC,QAAW/D,KAAS8D,EAAQ,CAC1B,IAAMvF,EAAI8E,EAAQ,IAAIrD,CAAK,EACtBzB,GACLwF,EAAQ,KAAK,CACX,MAAA/D,EACA,KAAMA,EAAM,WAAW,SAAS,EAAI,SAAW,SAC/C,QAASzB,EAAE,QACX,KAAMA,EAAE,OAAO,KACf,KAAMA,EAAE,OAAO,KACf,YAAaA,EAAE,OAAO,YACtB,GAAIA,EAAE,YAAc,CAAE,YAAaA,EAAE,WAAY,EAAI,CAAC,EACtD,GAAIA,EAAE,UAAU,OAAS,CAAE,SAAUA,EAAE,QAAS,EAAI,CAAC,EACrD,GAAIA,EAAE,OAAO,OAAS,CAAE,OAAQA,EAAE,OAAO,MAAO,EAAI,CAAC,EACrD,GAAIA,EAAE,OAAO,OAAS,CAAE,OAAQA,EAAE,OAAO,MAAO,EAAI,CAAC,EACrD,UAAWA,EAAE,OAAO,UACpB,QAASA,EAAE,OAAO,KAAK,MAAM,EAAGsF,EAAa,CAC/C,CAAC,EAEH,MAAO,CACL,OAAQ,CAAC,GAAG,IAAI,IAAIE,EAAQ,IAAKhI,GAAMA,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,EACzD,YAAa,KAAK,IAAI,EAAG,GAAGgI,EAAQ,IAAKhI,GAAMA,EAAE,aAAe,CAAC,CAAC,EAClE,QAAAgI,CACF,CACF,CAWO,SAASC,GACd3B,EAAiB4B,EAAY1D,EAAkF,CAAC,EACxF,CACxB,IAAMiD,EAAWU,EAAa7B,EAAS4B,CAAE,EACzC,GAAI,CAACT,EAAU,MAAO,CAAE,GAAI,GAAO,MAAO,gBAAgBS,IAAM,EAChE,GAAIT,EAAS,SAAW,UAAW,MAAO,CAAE,GAAI,GAAO,MAAO,aAAaS,iBAAkBT,EAAS,QAAS,EAE/G,IAAMlB,EAAM/B,EAAK,MAAQ,IAAM,CAAC,GAC1BtD,EAAQ,IAAIuF,EAAUH,EAAS,IAAII,IAAoBH,EAAIG,EAAK,IAAI,MAAM,EAAE,KAAK,GAAG,CAAC,CAAC,EACtF,CAAE,QAAA5B,CAAQ,EAAI2C,EACd/G,EAAWQ,EAAM,YAAY4D,EAAQ,IAAI,EAC/C,GAAI,CAACN,EAAK,MAAO,CACf,GAAI9D,GAAY,CAAC+G,EAAS,SACxB,MAAO,CAAE,GAAI,GAAO,MAAO,IAAI3C,EAAQ,6EAA8E,EAEvH,GAAIpE,GAAY+G,EAAS,UAAYE,EAAYjH,EAAS,OAAO,IAAM+G,EAAS,SAAS,YACvF,MAAO,CAAE,GAAI,GAAO,MAAO,IAAI3C,EAAQ,yEAA0E,EAGrH,IAAMJ,EAAMF,EAAK,KAAO,KAAK,IAAI,EAC3B4D,EAAQ,IAAI,KAAK1D,CAAG,EAAE,YAAY,EAAE,MAAM,EAAG,EAAE,EAiBrD,GAAI,CAhBOxD,EAAM,aACf4D,EAAQ,KACR,CACE,MAAOA,EAAQ,MACf,KAAMuD,EAAkBvD,EAAQ,IAAI,EAAIA,EAAQ,KAAO,OACvD,QAASA,EAAQ,QACjB,KAAMA,EAAQ,KACd,MAAOxF,EACP,OAAQ,SACR,QAASoB,GAAU,KAAK,SAAW0H,EACnC,YAAaA,EACb,QAAS3H,GAAaC,GAAU,KAAK,QAASoE,EAAQ,YAAY,CACpE,EACAA,EAAQ,QACRxF,CACF,EACS,MAAO,CAAE,GAAI,GAAO,MAAO,qBAAqBwF,EAAQ,mBAAmBpE,GAAU,KAAK,OAAS,MAAO,EAEnH,IAAMP,EAAK,IAAI,KAAKuE,CAAG,EAAE,YAAY,EACrC7C,EAAsByE,EAASxB,EAAQ,aAAa,IAAKb,IAAW,CAClE,MAAAA,EAAO,SAAU,WAAqB,QAASa,EAAQ,KAAM,SAAUoD,EAAI,GAAA/H,CAC7E,EAAE,CAAC,EACHe,EAAM,aAAa,EACnB,IAAMoH,EAA6B,CAAE,GAAGb,EAAU,OAAQ,WAAY,UAAWjD,EAAK,IAAM,WAAY,UAAWrE,CAAG,EACtH,OAAAyH,EAAatB,EAASgC,CAAO,EACtB,CAAE,GAAI,GAAM,SAAUA,CAAQ,CACvC,CAGO,SAASC,GACdjC,EAAiB4B,EAAY1D,EAAuD,CAAC,EAC7D,CACxB,IAAMiD,EAAWU,EAAa7B,EAAS4B,CAAE,EACzC,GAAI,CAACT,EAAU,MAAO,CAAE,GAAI,GAAO,MAAO,gBAAgBS,IAAM,EAChE,GAAIT,EAAS,SAAW,UAAW,MAAO,CAAE,GAAI,GAAO,MAAO,aAAaS,iBAAkBT,EAAS,QAAS,EAC/G,IAAMtH,EAAK,IAAI,KAAKqE,EAAK,KAAO,KAAK,IAAI,CAAC,EAAE,YAAY,EACxD3C,EAAsByE,EAASmB,EAAS,QAAQ,aAAa,IAAKxD,IAAW,CAC3E,MAAAA,EAAO,SAAU,WAAqB,QAASwD,EAAS,QAAQ,KAAM,SAAUS,EAAI,OAAQ1D,EAAK,OAAQ,GAAArE,CAC3G,EAAE,CAAC,EACH,IAAMmI,EAA6B,CACjC,GAAGb,EAAU,OAAQ,WAAY,UAAWjD,EAAK,IAAM,WAAY,UAAWrE,EAC9E,GAAIqE,EAAK,OAAS,CAAE,OAAQA,EAAK,MAAO,EAAI,CAAC,CAC/C,EACA,OAAAoD,EAAatB,EAASgC,CAAO,EACtB,CAAE,GAAI,GAAM,SAAUA,CAAQ,CACvC","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"]}
@@ -1,59 +0,0 @@
1
- import{a as u,b as o}from"./chunk-ZTEU5JVQ.js";o();function m(e,n,t=u()){return e.prepare(`
2
- INSERT INTO task_traces (
3
- task_id, agent_id, channel, chat_id, workflow_run_id, workflow_id,
4
- workflow_node_id, intent_event_id, intent_decided_by, resume_session_id,
5
- model, status, started_at, message_preview, original_message,
6
- resume_origin, resume_attempt, resumed_from
7
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'in-flight', ?, ?, ?, ?, ?, ?)
8
- `).run(t,n.agentId,n.channel??null,n.chatId??null,n.workflowRunId??null,n.workflowId??null,n.workflowNodeId??null,n.intentEventId??null,n.intentDecidedBy??null,n.resumeSessionId??null,n.model??null,Date.now(),n.messagePreview??null,n.originalMessage??null,n.resumeOrigin??null,n.resumeAttempt??0,n.resumedFrom??null),t}function g(e,n,t){let s=Date.now();e.prepare(`
9
- UPDATE task_traces SET
10
- status = ?,
11
- final_session_id = ?,
12
- input_tokens = ?,
13
- output_tokens = ?,
14
- cache_read_tokens = ?,
15
- cache_create_tokens = ?,
16
- tier2_input_tokens = ?,
17
- tier2_output_tokens = ?,
18
- tier2_cache_read_tokens = ?,
19
- tier2_cache_create_tokens = ?,
20
- resumed = COALESCE(?, resumed),
21
- resume_session_id = COALESCE(?, resume_session_id),
22
- jev_arm = COALESCE(?, jev_arm),
23
- error = ?,
24
- final_response = COALESCE(?, final_response),
25
- finished_at = ?,
26
- duration_ms = ? - started_at
27
- WHERE task_id = ?
28
- `).run(t.status,t.finalSessionId??null,t.inputTokens??null,t.outputTokens??null,t.cacheReadTokens??null,t.cacheCreateTokens??null,t.tier2InputTokens??null,t.tier2OutputTokens??null,t.tier2CacheReadTokens??null,t.tier2CacheCreateTokens??null,t.resumed==null?null:t.resumed?1:0,t.resumeSessionId??null,t.jevArm??null,t.error??null,t.finalResponse??null,s,s,n)}function _(e,n,t){let s=Date.now();return e.transaction(()=>{let l=(e.prepare("SELECT MAX(seq) AS max_seq FROM task_trace_steps WHERE task_id = ?").get(n)?.max_seq??-1)+1;return e.prepare(`
29
- INSERT INTO task_trace_steps (
30
- task_id, seq, name, action, status, input_summary, output_summary, error, ms, started_at
31
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
32
- `).run(n,l,t.name,t.action??null,t.status??null,t.inputSummary??null,t.outputSummary??null,t.error??null,t.ms??null,s),l})()}function i(e){return{taskId:e.task_id,agentId:e.agent_id,channel:e.channel??null,chatId:e.chat_id??null,workflowRunId:e.workflow_run_id??null,workflowId:e.workflow_id??null,workflowNodeId:e.workflow_node_id??null,intentEventId:e.intent_event_id??null,intentDecidedBy:e.intent_decided_by??null,resumeSessionId:e.resume_session_id??null,finalSessionId:e.final_session_id??null,model:e.model??null,status:e.status,startedAt:e.started_at,finishedAt:e.finished_at??null,durationMs:e.duration_ms??null,inputTokens:e.input_tokens??null,outputTokens:e.output_tokens??null,cacheReadTokens:e.cache_read_tokens??null,cacheCreateTokens:e.cache_create_tokens??null,tier2InputTokens:e.tier2_input_tokens??null,tier2OutputTokens:e.tier2_output_tokens??null,tier2CacheReadTokens:e.tier2_cache_read_tokens??null,tier2CacheCreateTokens:e.tier2_cache_create_tokens??null,resumed:e.resumed==null?null:e.resumed===1,jevArm:e.jev_arm??null,error:e.error??null,messagePreview:e.message_preview??null,originalMessage:e.original_message??null,finalResponse:e.final_response??null}}function d(e){return{taskId:e.task_id,seq:e.seq,name:e.name,action:e.action??null,status:e.status??null,inputSummary:e.input_summary??null,outputSummary:e.output_summary??null,error:e.error??null,ms:e.ms??null,startedAt:e.started_at}}function p(e,n){let t=e.prepare("SELECT * FROM task_traces WHERE task_id = ?").get(n);if(!t)return null;let s=e.prepare("SELECT * FROM task_trace_steps WHERE task_id = ? ORDER BY seq").all(n);return{task:i(t),steps:s.map(d)}}function k(e){let n=Date.now();return e.prepare(`
33
- UPDATE task_traces
34
- SET status = 'canceled',
35
- error = 'daemon-restart (orphaned in-flight)',
36
- finished_at = ?
37
- WHERE status = 'in-flight'
38
- `).run(n).changes??0}function h(e,n=Date.now()){let t=e.transaction(()=>{let r=e.prepare(`
39
- SELECT task_id, agent_id, channel, chat_id, workflow_run_id, started_at,
40
- original_message, resume_origin, resume_attempt
41
- FROM task_traces WHERE status = 'in-flight' ORDER BY started_at
42
- `).all();return e.prepare(`
43
- UPDATE task_traces
44
- SET status = 'canceled', error = 'daemon-restart (interrupted)',
45
- finished_at = ?, resume_decision = 'interrupted'
46
- WHERE status = 'in-flight'
47
- `).run(n),r}),s=e.prepare(`
48
- SELECT action, input_summary FROM task_trace_steps
49
- WHERE task_id = ? AND name = 'tool_use' ORDER BY seq
50
- `);return t().map(r=>({taskId:r.task_id,agentId:r.agent_id,channel:r.channel??null,chatId:r.chat_id??null,workflowRunId:r.workflow_run_id??null,startedAt:r.started_at,originalMessage:r.original_message??null,resumeOrigin:r.resume_origin??null,resumeAttempt:r.resume_attempt??0,toolCalls:s.all(r.task_id).map(a=>({action:a.action??null,inputSummary:a.input_summary??null}))}))}function T(e,n){return(e.prepare(`
51
- UPDATE task_traces SET resume_decision = 'deciding'
52
- WHERE task_id = ? AND resume_decision = 'interrupted'
53
- `).run(n).changes??0)===1}function I(e,n,t,s){e.prepare("UPDATE task_traces SET resume_decision = ?, resume_reason = ? WHERE task_id = ?").run(t,s.slice(0,500),n)}function f(e,n={}){let t=[],s=[];n.agentId&&(t.push("agent_id = ?"),s.push(n.agentId)),n.channel&&(t.push("channel = ?"),s.push(n.channel)),n.chatId&&(t.push("chat_id = ?"),s.push(n.chatId)),n.workflowRunId&&(t.push("workflow_run_id = ?"),s.push(n.workflowRunId)),n.status&&(t.push("status = ?"),s.push(n.status)),n.since!==void 0&&(t.push("started_at >= ?"),s.push(n.since)),n.until!==void 0&&(t.push("started_at <= ?"),s.push(n.until));let r=Math.max(1,Math.min(n.limit??100,1e3)),a=`
54
- SELECT * FROM task_traces
55
- ${t.length>0?"WHERE "+t.join(" AND "):""}
56
- ORDER BY started_at DESC
57
- LIMIT ?
58
- `;return s.push(r),e.prepare(a).all(...s).map(i)}export{m as a,g as b,_ as c,p as d,k as e,h as f,T as g,I as h,f as i};
59
- //# sourceMappingURL=chunk-WZGABQ72.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/storage/traces.ts"],"sourcesContent":["import type Database from \"better-sqlite3\"\nimport { newEventId } from \"@/intent/ulid\"\n\n// --- Task trace store ---\n//\n// Improvement plan #2 — per-task observability backed by SQLite. One\n// task_traces row per executeTask invocation, an append-only stream of\n// task_trace_steps inside it. The store is intentionally dumb: it stores\n// what callers give it. Callers (capture sites in src/agents/runtime.ts)\n// are responsible for byte-capping large step payloads so the daemon\n// memory stays bounded under tool-heavy turns.\n//\n// Why a separate store layer (vs writing inline in runtime.ts)?\n// - Tests can exercise the trace lifecycle against a temp SQLite without\n// spinning up the agent runtime.\n// - The HTTP endpoints (GET /task/:id/trace) read through the same\n// functions producers write through, so there's a single source of\n// truth on field names + semantics.\n// - Future capture sites (sdk tier, orchestrator tier) reuse this API\n// without copying SQL.\n//\n// Why ULID for task_id?\n// - URL-safe in HTTP routes (GET /task/:id/trace).\n// - Time-sortable, so listTraces ORDER BY started_at and ORDER BY\n// task_id agree without an explicit timestamp column index.\n// - Already in-tree (src/intent/ulid.ts); no new dep.\n//\n// Cross-DB FK note:\n// intent_event_id and intent_decided_by are logical foreign keys into\n// the intent ledger at .agentx/intent/ledger.sqlite. better-sqlite3\n// doesn't enforce cross-database FKs and we don't ATTACH the ledger\n// here on purpose — the rescue plan's append-only contract on the\n// ledger is honoured by routing all writes through src/intent/ledger.ts.\n// Joins happen at the HTTP / CLI layer when needed.\n\nexport interface TraceStartInput {\n agentId: string\n channel?: string | null\n chatId?: string | null\n messagePreview?: string | null\n /** Full untruncated user message — used by `agentx trace replay <taskId>`\n * to re-fire a recorded task against the current agent config. NULL on\n * rows from before migration v8, in which case replay falls back to\n * messagePreview (which is capped at 200 chars). */\n originalMessage?: string | null\n workflowRunId?: string | null\n workflowId?: string | null\n workflowNodeId?: string | null\n intentEventId?: string | null\n intentDecidedBy?: string | null\n resumeSessionId?: string | null\n model?: string | null\n /** How to re-enter this run after a restart (JSON, see agents/resume). */\n resumeOrigin?: string | null\n /** 0 for a fresh run; n for the n-th resume of a cut-off run. */\n resumeAttempt?: number | null\n /** The cut-off run this one continues. */\n resumedFrom?: string | null\n}\n\nexport interface TraceEndInput {\n status: \"ok\" | \"error\" | \"timeout\"\n finalSessionId?: string | null\n inputTokens?: number | null\n outputTokens?: number | null\n cacheReadTokens?: number | null\n cacheCreateTokens?: number | null\n tier2InputTokens?: number | null\n tier2OutputTokens?: number | null\n tier2CacheReadTokens?: number | null\n tier2CacheCreateTokens?: number | null\n /** Whether the turn continued a provider session. The resume decision\n * is made after task:started, so it is recorded at the end. Omitted\n * leaves both resumed and resume_session_id untouched. */\n resumed?: boolean | null\n resumeSessionId?: string | null\n /** Request-gate experiment arm: \"treatment\" | \"holdout\". */\n jevArm?: string | null\n error?: string | null\n /** The agent's final reply text. Stored verbatim so `replay --diff`\n * can compare original output vs the new run's output without\n * reconstructing it from the step ledger. NULL when the response\n * wasn't captured (older rows or non-text outputs). */\n finalResponse?: string | null\n}\n\nexport interface TraceStepInput {\n /** Coarse step kind. Conventional values: \"tool_use\" | \"tool_result\" |\n * \"llm_message\" | \"session_rotation\" | \"error\" | \"preflight\". Free-form\n * strings allowed so future capture sites don't need a schema bump. */\n name: string\n /** Fine-grained name within the kind. For \"tool_use\", this is the tool\n * name (e.g. \"Bash\", \"Edit\"). Optional. */\n action?: string | null\n status?: \"ok\" | \"error\" | \"in-flight\" | null\n /** JSON-stringified input or human-readable summary. Caller-byte-capped. */\n inputSummary?: string | null\n outputSummary?: string | null\n error?: string | null\n /** Step duration when known. Optional — many step kinds are point-in-time. */\n ms?: number | null\n}\n\nexport interface TraceRecord {\n taskId: string\n agentId: string\n channel: string | null\n chatId: string | null\n workflowRunId: string | null\n workflowId: string | null\n workflowNodeId: string | null\n intentEventId: string | null\n intentDecidedBy: string | null\n resumeSessionId: string | null\n finalSessionId: string | null\n model: string | null\n status: string\n startedAt: number\n finishedAt: number | null\n durationMs: number | null\n inputTokens: number | null\n outputTokens: number | null\n cacheReadTokens: number | null\n cacheCreateTokens: number | null\n tier2InputTokens: number | null\n tier2OutputTokens: number | null\n tier2CacheReadTokens: number | null\n tier2CacheCreateTokens: number | null\n /** NULL on rows recorded before migration v12. */\n resumed: boolean | null\n /** NULL when the request gate was not active (or before migration v13). */\n jevArm: string | null\n error: string | null\n messagePreview: string | null\n /** Full untruncated user message — populated for traces from migration v8\n * onward. NULL on older rows; consumers should fall back to messagePreview. */\n originalMessage: string | null\n /** Agent's final reply text — populated for traces from migration v8\n * onward when the executor records it on end. */\n finalResponse: string | null\n}\n\nexport interface TraceStepRecord {\n taskId: string\n seq: number\n name: string\n action: string | null\n status: string | null\n inputSummary: string | null\n outputSummary: string | null\n error: string | null\n ms: number | null\n startedAt: number\n}\n\nexport interface ListTracesFilters {\n agentId?: string\n channel?: string\n chatId?: string\n workflowRunId?: string\n status?: string\n /** ms epoch */\n since?: number\n /** ms epoch */\n until?: number\n /** Default 100, capped at 1000. */\n limit?: number\n}\n\n/**\n * Insert an in-flight trace row and return its ULID. Callers thread the\n * returned id through the executor and call recordTraceEnd at finish time.\n *\n * `taskId` is optional — when supplied, the caller has already allocated\n * a ULID upstream (e.g. registry.execute generating it before the bus\n * event so the runtime can capture per-step rows under the same id).\n * When omitted, a fresh ULID is allocated here.\n */\nexport function recordTraceStart(\n db: Database.Database,\n input: TraceStartInput,\n taskId: string = newEventId(),\n): string {\n db.prepare(`\n INSERT INTO task_traces (\n task_id, agent_id, channel, chat_id, workflow_run_id, workflow_id,\n workflow_node_id, intent_event_id, intent_decided_by, resume_session_id,\n model, status, started_at, message_preview, original_message,\n resume_origin, resume_attempt, resumed_from\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'in-flight', ?, ?, ?, ?, ?, ?)\n `).run(\n taskId,\n input.agentId,\n input.channel ?? null,\n input.chatId ?? null,\n input.workflowRunId ?? null,\n input.workflowId ?? null,\n input.workflowNodeId ?? null,\n input.intentEventId ?? null,\n input.intentDecidedBy ?? null,\n input.resumeSessionId ?? null,\n input.model ?? null,\n Date.now(),\n input.messagePreview ?? null,\n input.originalMessage ?? null,\n input.resumeOrigin ?? null,\n input.resumeAttempt ?? 0,\n input.resumedFrom ?? null,\n )\n return taskId\n}\n\n/**\n * Finalize a trace. duration_ms is computed from finished_at - started_at\n * inside the UPDATE so the two columns can never disagree. Idempotent —\n * calling twice on the same task_id overwrites the prior end values; the\n * second call's status wins. (Soft-overwrite is intentional: timeout\n * handlers may write before a late stream-completion arrives.)\n */\nexport function recordTraceEnd(db: Database.Database, taskId: string, input: TraceEndInput): void {\n const finishedAt = Date.now()\n db.prepare(`\n UPDATE task_traces SET\n status = ?,\n final_session_id = ?,\n input_tokens = ?,\n output_tokens = ?,\n cache_read_tokens = ?,\n cache_create_tokens = ?,\n tier2_input_tokens = ?,\n tier2_output_tokens = ?,\n tier2_cache_read_tokens = ?,\n tier2_cache_create_tokens = ?,\n resumed = COALESCE(?, resumed),\n resume_session_id = COALESCE(?, resume_session_id),\n jev_arm = COALESCE(?, jev_arm),\n error = ?,\n final_response = COALESCE(?, final_response),\n finished_at = ?,\n duration_ms = ? - started_at\n WHERE task_id = ?\n `).run(\n input.status,\n input.finalSessionId ?? null,\n input.inputTokens ?? null,\n input.outputTokens ?? null,\n input.cacheReadTokens ?? null,\n input.cacheCreateTokens ?? null,\n input.tier2InputTokens ?? null,\n input.tier2OutputTokens ?? null,\n input.tier2CacheReadTokens ?? null,\n input.tier2CacheCreateTokens ?? null,\n input.resumed == null ? null : input.resumed ? 1 : 0,\n input.resumeSessionId ?? null,\n input.jevArm ?? null,\n input.error ?? null,\n input.finalResponse ?? null,\n finishedAt,\n finishedAt,\n taskId,\n )\n}\n\n/**\n * Append a step. seq is auto-allocated as MAX(seq)+1 within a transaction\n * so concurrent step writes for the same task_id can't collide. Returns\n * the assigned seq, useful for downstream correlation.\n */\nexport function recordTraceStep(db: Database.Database, taskId: string, input: TraceStepInput): number {\n const startedAt = Date.now()\n const allocateAndInsert = db.transaction((): number => {\n const row = db\n .prepare(\"SELECT MAX(seq) AS max_seq FROM task_trace_steps WHERE task_id = ?\")\n .get(taskId) as { max_seq: number | null } | undefined\n const seq = ((row?.max_seq ?? -1) as number) + 1\n db.prepare(`\n INSERT INTO task_trace_steps (\n task_id, seq, name, action, status, input_summary, output_summary, error, ms, started_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n taskId,\n seq,\n input.name,\n input.action ?? null,\n input.status ?? null,\n input.inputSummary ?? null,\n input.outputSummary ?? null,\n input.error ?? null,\n input.ms ?? null,\n startedAt,\n )\n return seq\n })\n return allocateAndInsert()\n}\n\nfunction rowToTrace(row: Record<string, unknown>): TraceRecord {\n return {\n taskId: row.task_id as string,\n agentId: row.agent_id as string,\n channel: (row.channel as string) ?? null,\n chatId: (row.chat_id as string) ?? null,\n workflowRunId: (row.workflow_run_id as string) ?? null,\n workflowId: (row.workflow_id as string) ?? null,\n workflowNodeId: (row.workflow_node_id as string) ?? null,\n intentEventId: (row.intent_event_id as string) ?? null,\n intentDecidedBy: (row.intent_decided_by as string) ?? null,\n resumeSessionId: (row.resume_session_id as string) ?? null,\n finalSessionId: (row.final_session_id as string) ?? null,\n model: (row.model as string) ?? null,\n status: row.status as string,\n startedAt: row.started_at as number,\n finishedAt: (row.finished_at as number) ?? null,\n durationMs: (row.duration_ms as number) ?? null,\n inputTokens: (row.input_tokens as number) ?? null,\n outputTokens: (row.output_tokens as number) ?? null,\n cacheReadTokens: (row.cache_read_tokens as number) ?? null,\n cacheCreateTokens: (row.cache_create_tokens as number) ?? null,\n tier2InputTokens: (row.tier2_input_tokens as number) ?? null,\n tier2OutputTokens: (row.tier2_output_tokens as number) ?? null,\n tier2CacheReadTokens: (row.tier2_cache_read_tokens as number) ?? null,\n tier2CacheCreateTokens: (row.tier2_cache_create_tokens as number) ?? null,\n resumed: row.resumed == null ? null : row.resumed === 1,\n jevArm: (row.jev_arm as string) ?? null,\n error: (row.error as string) ?? null,\n messagePreview: (row.message_preview as string) ?? null,\n originalMessage: (row.original_message as string) ?? null,\n finalResponse: (row.final_response as string) ?? null,\n }\n}\n\nfunction rowToStep(row: Record<string, unknown>): TraceStepRecord {\n return {\n taskId: row.task_id as string,\n seq: row.seq as number,\n name: row.name as string,\n action: (row.action as string) ?? null,\n status: (row.status as string) ?? null,\n inputSummary: (row.input_summary as string) ?? null,\n outputSummary: (row.output_summary as string) ?? null,\n error: (row.error as string) ?? null,\n ms: (row.ms as number) ?? null,\n startedAt: row.started_at as number,\n }\n}\n\n/** Fetch a trace + its ordered steps. Returns null if no such task_id. */\nexport function getTrace(\n db: Database.Database,\n taskId: string,\n): { task: TraceRecord; steps: TraceStepRecord[] } | null {\n const row = db.prepare(\"SELECT * FROM task_traces WHERE task_id = ?\").get(taskId) as\n | Record<string, unknown>\n | undefined\n if (!row) return null\n const stepRows = db\n .prepare(\"SELECT * FROM task_trace_steps WHERE task_id = ? ORDER BY seq\")\n .all(taskId) as Record<string, unknown>[]\n return { task: rowToTrace(row), steps: stepRows.map(rowToStep) }\n}\n\n/**\n * Cancel any in-flight trace rows left over from a previous daemon\n * lifetime. Mirrors the intent ledger's startup cleanup (rescue plan\n * Phase 1 commit a8514d9): a hard-killed daemon never gets to fire\n * task:completed, so the row sits \"in-flight\" forever and contaminates\n * triage queries (`agentx trace list --status in-flight`) with stale\n * orphans. Run once at daemon boot before subscribers attach.\n *\n * Returns the number of rows updated. duration_ms is intentionally NOT\n * computed — we don't know when the task actually ended; preserving\n * NULL there is more honest than backdating to \"now\".\n */\nexport function cleanupOrphanedTraces(db: Database.Database): number {\n const now = Date.now()\n const r = db.prepare(`\n UPDATE task_traces\n SET status = 'canceled',\n error = 'daemon-restart (orphaned in-flight)',\n finished_at = ?\n WHERE status = 'in-flight'\n `).run(now)\n return (r.changes ?? 0) as number\n}\n\n// --- Resume after restart (#103) -------------------------------------------\n\n/** A run that was still going when the previous daemon stopped. */\nexport interface InterruptedRun {\n taskId: string\n agentId: string\n channel: string | null\n chatId: string | null\n workflowRunId: string | null\n startedAt: number\n originalMessage: string | null\n resumeOrigin: string | null\n resumeAttempt: number\n /** Tool calls it had made, in order, for the resume note. */\n toolCalls: Array<{ action: string | null; inputSummary: string | null }>\n}\n\n/** Close every run left in flight by the previous daemon, marking it\n * `interrupted` for the resume planner, and return them. One transaction,\n * so a second process booting on the same database gets nothing. */\nexport function takeInterruptedRuns(db: Database.Database, now = Date.now()): InterruptedRun[] {\n const take = db.transaction(() => {\n const rows = db.prepare(`\n SELECT task_id, agent_id, channel, chat_id, workflow_run_id, started_at,\n original_message, resume_origin, resume_attempt\n FROM task_traces WHERE status = 'in-flight' ORDER BY started_at\n `).all() as Array<Record<string, any>>\n db.prepare(`\n UPDATE task_traces\n SET status = 'canceled', error = 'daemon-restart (interrupted)',\n finished_at = ?, resume_decision = 'interrupted'\n WHERE status = 'in-flight'\n `).run(now)\n return rows\n })\n const steps = db.prepare(`\n SELECT action, input_summary FROM task_trace_steps\n WHERE task_id = ? AND name = 'tool_use' ORDER BY seq\n `)\n return take().map((r) => ({\n taskId: r.task_id,\n agentId: r.agent_id,\n channel: r.channel ?? null,\n chatId: r.chat_id ?? null,\n workflowRunId: r.workflow_run_id ?? null,\n startedAt: r.started_at,\n originalMessage: r.original_message ?? null,\n resumeOrigin: r.resume_origin ?? null,\n resumeAttempt: r.resume_attempt ?? 0,\n toolCalls: (steps.all(r.task_id) as Array<Record<string, any>>).map((s) => ({\n action: s.action ?? null, inputSummary: s.input_summary ?? null,\n })),\n }))\n}\n\n/** Claim an interrupted run before deciding what to do with it. Returns\n * false when another process already claimed or decided it, so a run is\n * never resumed — or its chat told — twice. */\nexport function claimResume(db: Database.Database, taskId: string): boolean {\n const r = db.prepare(`\n UPDATE task_traces SET resume_decision = 'deciding'\n WHERE task_id = ? AND resume_decision = 'interrupted'\n `).run(taskId)\n return (r.changes ?? 0) === 1\n}\n\nexport type ResumeDecision = \"resumed\" | \"reported\" | \"skipped\" | \"resume-failed\"\n\nexport function recordResumeDecision(\n db: Database.Database, taskId: string, decision: ResumeDecision, reason: string,\n): void {\n db.prepare(\"UPDATE task_traces SET resume_decision = ?, resume_reason = ? WHERE task_id = ?\")\n .run(decision, reason.slice(0, 500), taskId)\n}\n\n/** Triage / dashboard surface — list traces newest-first, filtered. */\nexport function listTraces(db: Database.Database, filters: ListTracesFilters = {}): TraceRecord[] {\n const where: string[] = []\n const params: unknown[] = []\n if (filters.agentId) { where.push(\"agent_id = ?\"); params.push(filters.agentId) }\n if (filters.channel) { where.push(\"channel = ?\"); params.push(filters.channel) }\n if (filters.chatId) { where.push(\"chat_id = ?\"); params.push(filters.chatId) }\n if (filters.workflowRunId) { where.push(\"workflow_run_id = ?\"); params.push(filters.workflowRunId) }\n if (filters.status) { where.push(\"status = ?\"); params.push(filters.status) }\n if (filters.since !== undefined) { where.push(\"started_at >= ?\"); params.push(filters.since) }\n if (filters.until !== undefined) { where.push(\"started_at <= ?\"); params.push(filters.until) }\n\n const limit = Math.max(1, Math.min(filters.limit ?? 100, 1000))\n const sql = `\n SELECT * FROM task_traces\n ${where.length > 0 ? \"WHERE \" + where.join(\" AND \") : \"\"}\n ORDER BY started_at DESC\n LIMIT ?\n `\n params.push(limit)\n return (db.prepare(sql).all(...params) as Record<string, unknown>[]).map(rowToTrace)\n}\n"],"mappings":"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","names":["init_ulid","recordTraceStart","db","input","taskId","newEventId","recordTraceEnd","finishedAt","recordTraceStep","startedAt","seq","rowToTrace","row","rowToStep","getTrace","stepRows","cleanupOrphanedTraces","now","takeInterruptedRuns","take","rows","steps","s","claimResume","recordResumeDecision","decision","reason","listTraces","filters","where","params","limit","sql"]}