wyrm-mcp 7.3.2 → 7.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/README.md +8 -14
  2. package/dist/activation.js +59 -1
  3. package/dist/agent-daemon.js +281 -4
  4. package/dist/agent-loop.js +332 -7
  5. package/dist/analytics.js +236 -13
  6. package/dist/attribution.js +49 -1
  7. package/dist/audit.js +457 -2
  8. package/dist/auto-capture.js +138 -3
  9. package/dist/auto-orchestrator.js +325 -1
  10. package/dist/autoconfig.d.ts +50 -0
  11. package/dist/autoconfig.d.ts.map +1 -1
  12. package/dist/autoconfig.js +1115 -39
  13. package/dist/autoconfig.js.map +1 -1
  14. package/dist/buddy-runner.js +109 -1
  15. package/dist/buddy.js +564 -14
  16. package/dist/build-flags.js +15 -1
  17. package/dist/capabilities.js +183 -3
  18. package/dist/capture.js +56 -1
  19. package/dist/causality.js +148 -8
  20. package/dist/cli.js +281 -20
  21. package/dist/cloud/cli.js +541 -5
  22. package/dist/cloud/client.js +221 -1
  23. package/dist/cloud/crypto.js +85 -1
  24. package/dist/cloud/machine-id.js +113 -2
  25. package/dist/cloud/recovery.js +60 -1
  26. package/dist/cloud/sync-engine.js +543 -7
  27. package/dist/cloud-backup.js +579 -5
  28. package/dist/cloud-profile.js +138 -1
  29. package/dist/cloud-sync-entrypoint.js +47 -1
  30. package/dist/cloud-sync.js +309 -2
  31. package/dist/connectors/bridge-source.d.ts +46 -0
  32. package/dist/connectors/bridge-source.d.ts.map +1 -0
  33. package/dist/connectors/bridge-source.js +77 -0
  34. package/dist/connectors/bridge-source.js.map +1 -0
  35. package/dist/connectors/index.d.ts +24 -0
  36. package/dist/connectors/index.d.ts.map +1 -0
  37. package/dist/connectors/index.js +69 -0
  38. package/dist/connectors/index.js.map +1 -0
  39. package/dist/connectors/ingest.d.ts +16 -0
  40. package/dist/connectors/ingest.d.ts.map +1 -0
  41. package/dist/connectors/ingest.js +116 -0
  42. package/dist/connectors/ingest.js.map +1 -0
  43. package/dist/connectors/types.d.ts +99 -0
  44. package/dist/connectors/types.d.ts.map +1 -0
  45. package/dist/connectors/types.js +17 -0
  46. package/dist/connectors/types.js.map +1 -0
  47. package/dist/constellation.js +168 -12
  48. package/dist/content-signature.js +45 -1
  49. package/dist/context-build-budgeted.js +144 -4
  50. package/dist/context-ranking.js +69 -1
  51. package/dist/crypto.js +179 -1
  52. package/dist/daemon-write-endpoint.js +290 -1
  53. package/dist/daemon-writer.js +406 -2
  54. package/dist/database.js +1278 -53
  55. package/dist/deprecations.js +162 -2
  56. package/dist/design.js +141 -13
  57. package/dist/event-replication.js +112 -1
  58. package/dist/events-sse.js +43 -7
  59. package/dist/events.js +238 -6
  60. package/dist/failure-patterns.d.ts +107 -0
  61. package/dist/failure-patterns.d.ts.map +1 -1
  62. package/dist/failure-patterns.js +924 -43
  63. package/dist/failure-patterns.js.map +1 -1
  64. package/dist/federation.js +236 -12
  65. package/dist/goals.js +101 -13
  66. package/dist/golden.js +355 -3
  67. package/dist/handlers/agent.js +165 -4
  68. package/dist/handlers/alias-adapters.js +129 -1
  69. package/dist/handlers/aliases.js +171 -1
  70. package/dist/handlers/audit.js +87 -1
  71. package/dist/handlers/boundary.js +221 -1
  72. package/dist/handlers/capture.js +1114 -73
  73. package/dist/handlers/causality.js +119 -9
  74. package/dist/handlers/cloud.js +382 -85
  75. package/dist/handlers/companion.js +459 -28
  76. package/dist/handlers/datalake.js +187 -7
  77. package/dist/handlers/dispatch-context.js +22 -0
  78. package/dist/handlers/entity.js +256 -25
  79. package/dist/handlers/events.js +335 -16
  80. package/dist/handlers/failure.d.ts.map +1 -1
  81. package/dist/handlers/failure.js +408 -13
  82. package/dist/handlers/failure.js.map +1 -1
  83. package/dist/handlers/goals.js +296 -4
  84. package/dist/handlers/intelligence.js +681 -126
  85. package/dist/handlers/invoicing.js +70 -1
  86. package/dist/handlers/mcpclient.js +137 -6
  87. package/dist/handlers/orchestration.js +125 -40
  88. package/dist/handlers/output-schemas.js +24 -1
  89. package/dist/handlers/presence.js +99 -3
  90. package/dist/handlers/project.js +182 -28
  91. package/dist/handlers/prompts.js +157 -6
  92. package/dist/handlers/quest.js +224 -4
  93. package/dist/handlers/recall.js +237 -13
  94. package/dist/handlers/registry.js +167 -1
  95. package/dist/handlers/resources.js +288 -1
  96. package/dist/handlers/review.js +74 -11
  97. package/dist/handlers/run.js +498 -16
  98. package/dist/handlers/search.js +338 -15
  99. package/dist/handlers/session.js +643 -31
  100. package/dist/handlers/share.js +184 -8
  101. package/dist/handlers/shims.js +464 -1
  102. package/dist/handlers/skill.js +449 -67
  103. package/dist/handlers/survivors.js +120 -1
  104. package/dist/handlers/symbols.js +109 -8
  105. package/dist/handlers/syncops.js +302 -4
  106. package/dist/handlers/types.js +27 -1
  107. package/dist/harvest.js +191 -5
  108. package/dist/hours.js +156 -7
  109. package/dist/http-auth.js +321 -3
  110. package/dist/http-fast.js +1302 -22
  111. package/dist/icons.js +47 -1
  112. package/dist/importers.js +268 -1
  113. package/dist/index.js +840 -2
  114. package/dist/indexer.js +145 -4
  115. package/dist/intelligence.js +261 -31
  116. package/dist/internal-dispatch.js +212 -3
  117. package/dist/keyset.js +110 -1
  118. package/dist/knowledge-graph.js +176 -12
  119. package/dist/license.js +441 -2
  120. package/dist/logger.js +199 -2
  121. package/dist/maintenance.js +148 -2
  122. package/dist/mcp-client.js +262 -6
  123. package/dist/memory-artifacts.js +596 -32
  124. package/dist/migrate-prompt.js +124 -2
  125. package/dist/migrations.d.ts.map +1 -1
  126. package/dist/migrations.js +799 -42
  127. package/dist/migrations.js.map +1 -1
  128. package/dist/performance.js +228 -1
  129. package/dist/presence.js +140 -11
  130. package/dist/priority-embed.js +164 -5
  131. package/dist/providers/embedding-provider.js +196 -1
  132. package/dist/readonly-gate.js +29 -1
  133. package/dist/receipt.js +43 -1
  134. package/dist/rehydration.js +157 -9
  135. package/dist/reindex.js +88 -1
  136. package/dist/render-target.js +544 -21
  137. package/dist/render.js +280 -4
  138. package/dist/repl-guard.js +173 -1
  139. package/dist/replication-daemon-entrypoint.js +31 -1
  140. package/dist/replication-daemon.js +262 -2
  141. package/dist/rerank.js +142 -1
  142. package/dist/resilience.js +591 -1
  143. package/dist/reverse-bridge.js +360 -5
  144. package/dist/security.js +244 -1
  145. package/dist/session-seen.js +51 -3
  146. package/dist/setup.js +260 -1
  147. package/dist/skill-author.js +168 -5
  148. package/dist/spec-kit.js +191 -1
  149. package/dist/sqlite-busy.js +154 -1
  150. package/dist/statusline.js +315 -11
  151. package/dist/sub-agent.js +262 -13
  152. package/dist/summarizer.js +139 -13
  153. package/dist/symbols.js +283 -7
  154. package/dist/sync.js +359 -5
  155. package/dist/tasks-dispatch.js +84 -1
  156. package/dist/tasks.js +282 -1
  157. package/dist/token-budget.js +143 -1
  158. package/dist/tool-analytics.js +129 -7
  159. package/dist/tool-annotations.js +365 -1
  160. package/dist/tool-manifest-v2.json +1 -1
  161. package/dist/tool-manifest.json +1 -1
  162. package/dist/tool-profiles.js +75 -1
  163. package/dist/trace-harvest.js +244 -6
  164. package/dist/types.js +30 -1
  165. package/dist/ui-dashboard.js +50 -41
  166. package/dist/ulid.js +81 -1
  167. package/dist/usage-tracker.js +66 -1
  168. package/dist/validate.js +129 -1
  169. package/dist/vault.js +534 -1
  170. package/dist/vector-init.js +67 -1
  171. package/dist/vectors.js +184 -3
  172. package/dist/version-check.js +136 -4
  173. package/dist/visibility.js +155 -19
  174. package/dist/wyrm-cli.js +2845 -101
  175. package/dist/wyrm-cli.js.map +1 -1
  176. package/dist/wyrm-guard.d.ts.map +1 -1
  177. package/dist/wyrm-guard.js +475 -14
  178. package/dist/wyrm-guard.js.map +1 -1
  179. package/dist/wyrm-loop.js +150 -3
  180. package/dist/wyrm-manifest.json +1 -1
  181. package/dist/wyrm-statusline-daemon.js +11 -1
  182. package/dist/wyrm-statusline.js +56 -4
  183. package/dist/wyrm-ui.js +77 -9
  184. package/package.json +1 -1
@@ -1,6 +1,244 @@
1
- const l=/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;function g(s){if(!s)return"";let o=s;const e=[[/\b(?:sk|rk|pk)[-_][A-Za-z0-9][A-Za-z0-9_-]{14,}\b/g,"[REDACTED]"],[/\bgh[posru]_[A-Za-z0-9]{20,}\b/g,"[REDACTED]"],[/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,"[REDACTED]"],[/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,"[REDACTED]"],[/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g,"[REDACTED]"],[/\bAIza[A-Za-z0-9_-]{30,}\b/g,"[REDACTED]"],[/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,"[REDACTED]"],[/\b([Bb]earer)\s+[A-Za-z0-9._-]{12,}/g,"$1 [REDACTED]"],[/((?:api[_-]?key|secret|token|password|passwd|auth(?:orization)?|access[_-]?token|private[_-]?key|client[_-]?secret)["']?\s*[:=]\s*["']?)([A-Za-z0-9._/+\-]{6,})/gi,"$1[REDACTED]"]];for(const[r,t]of e)o=o.replace(r,t);return o}function u(s){if(s==null)return"";if(typeof s=="string")return s;if(!Array.isArray(s))return"";const o=[];for(const e of s){if(typeof e=="string"){o.push(e);continue}if(e&&typeof e=="object"){const r=e;r.type==="text"&&typeof r.text=="string"&&o.push(r.text)}}return o.join(`
2
- `)}function p(s){return g(s).replace(l,"").replace(/[ \t]+/g," ").replace(/\n{3,}/g,`
3
-
4
- `).trim().slice(0,4e3)}function i(s){const o=s.trim();if(!o)return[];if(o.startsWith("["))try{const r=JSON.parse(o);if(Array.isArray(r))return r.filter(t=>!!t&&typeof t=="object")}catch{}const e=[];for(const r of o.split(`
5
- `)){const t=r.trim();if(!(!t||t[0]!=="{"&&t[0]!=="["))try{const c=JSON.parse(t);c&&typeof c=="object"&&!Array.isArray(c)&&e.push(c)}catch{}}return e}function A(s){const o=[];for(const e of i(s)){const r=e.message&&typeof e.message=="object"?e.message:e,t=String(r.role??e.role??"");if(t!=="user"&&t!=="assistant")continue;const c=u(r.content).trim();if(!c||t==="user"&&(c.startsWith("<")||c.startsWith("[{")))continue;const n=p(c);n.length>=12&&o.push({role:t,text:n})}return o}function m(s){const o=[],e=[["prompt","user"],["input","user"],["response","assistant"],["output","assistant"],["text","note"],["content","note"]];for(const r of i(s))for(const[t,c]of e){const n=r[t],a=typeof n=="string"?n:u(n);if(!a)continue;const f=p(a);f.length>=12&&o.push({role:c,text:f})}return o}function b(s){const o=new Map,e=[];for(const t of i(s)){const c=String(t.kind??"");let n="";c==="tool_call"?n=String(t.actor??t.tool??""):typeof t.tool_name=="string"?n=t.tool_name:typeof t.tool=="string"&&(n=t.tool),n=n.replace(l,"").trim().slice(0,80),n&&(o.has(n)||e.push(n),o.set(n,(o.get(n)??0)+1))}return e.length===0?[]:[{role:"tool",text:`Tool activity in this run: ${e.map(t=>`${t}\xD7${o.get(t)}`).join(", ")}.`}]}function x(s){const o=i(s).slice(0,50);if(o.length===0)return"claude-jsonl";let e=0,r=0;for(const t of o){(t.kind==="tool_call"||typeof t.tool_name=="string")&&e++;const n=(t.message&&typeof t.message=="object"?t.message:t).role??t.role;(n==="user"||n==="assistant"||n==="system")&&r++}return e>0&&e>=r?"tool-calls":r>0?"claude-jsonl":"dragon"}function y(s,o="auto"){const e=o==="auto"?x(s):o;return{segments:e==="tool-calls"?b(s):e==="dragon"?m(s):A(s),format:e}}function h(s,o=24e3){const e=[];let r=0;for(const t of s){const c=t.text;if(r+c.length>o)break;e.push(c),r+=c.length+1}return e.join(`
6
- `)}export{x as detectTraceFormat,A as parseClaudeJsonl,m as parseDragonTrace,b as parseToolCalls,y as parseTrace,g as redactSecrets,h as segmentsToText};
1
+ /**
2
+ * Trace harvest (v7 F4 T040) — turn a harness's working trace into run-tagged
3
+ * review-queue candidates, OFFLINE.
4
+ *
5
+ * Three trace shapes are ingested into one normalized prose stream that feeds
6
+ * the EXISTING auto_capture extractor (src/auto-capture.ts local Ollama
7
+ * WYRM_EXTRACT_MODEL / deterministic fallback, NEVER a cloud LLM, Article III):
8
+ *
9
+ * 1. Claude Code session JSONL — one JSON object per line, each
10
+ * { message: { role, content }, … } (the same shape
11
+ * scripts/hooks/wyrm-session-capture.mjs already parses). User prose +
12
+ * assistant text are kept; tool-result / system-reminder blocks dropped.
13
+ * 2. ~/.dragon/traces — the dragon-cli flywheel trace records. Tolerant of
14
+ * both line-delimited JSON and a top-level JSON array; each record's
15
+ * prompt/response/text/content fields are harvested.
16
+ * 3. WYRM_TRACE_TOOL_CALLS output — the `tool_call` Live Memory events the
17
+ * PostToolUse hook emits ({ kind:'tool_call', actor, payload, … }) or the
18
+ * raw stdin shape the hook reads ({ tool_name, tool_input, … }). Collapsed
19
+ * into a deterministic per-tool activity summary.
20
+ *
21
+ * SECRET REDACTION: every harvested segment passes through redactSecrets()
22
+ * BEFORE extraction, so an API key / bearer token / password that scrolled
23
+ * through a transcript never lands in the review queue. Deterministic, no
24
+ * network. Tool-call payloads are collapsed to keys (never values) for the same
25
+ * reason — a secret passed as a tool argument is summarized, not stored.
26
+ *
27
+ * Pure: parsing + redaction are total string functions (no I/O, no clock, no
28
+ * RNG). The MCP handler injects the DB writes + the extractor.
29
+ *
30
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd.
31
+ * @license Proprietary — (c) 2026 Ghost Protocol (Pvt) Ltd. All rights reserved. See LICENSE.
32
+ */
33
+ /** Control chars (ANSI/BEL/NUL) must never reach the review queue or a TUI. */
34
+ // eslint-disable-next-line no-control-regex
35
+ const CTRL = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
36
+ /**
37
+ * Redact common secret shapes from a string so they never enter the review
38
+ * queue. Deterministic + offline. Conservative (favours over-redaction): the
39
+ * goal is "a leaked credential is never stored", not perfect classification.
40
+ * Each rule replaces the SECRET portion with `[REDACTED]`, preserving
41
+ * surrounding prose so the candidate is still meaningful.
42
+ */
43
+ export function redactSecrets(input) {
44
+ if (!input)
45
+ return '';
46
+ let s = input;
47
+ // High-entropy / vendor-prefixed token shapes (order matters — specific first).
48
+ const rules = [
49
+ // sk-... / sk-ant-api03-... / rk_live_... (OpenAI/Anthropic/Stripe-style);
50
+ // the token body may carry internal -/_ separators (sk-ant-api03-<rand>).
51
+ [/\b(?:sk|rk|pk)[-_][A-Za-z0-9][A-Za-z0-9_-]{14,}\b/g, '[REDACTED]'],
52
+ // GitHub tokens (ghp_/gho_/ghu_/ghs_/ghr_) + fine-grained github_pat_
53
+ [/\bgh[posru]_[A-Za-z0-9]{20,}\b/g, '[REDACTED]'],
54
+ [/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, '[REDACTED]'],
55
+ // AWS access key id
56
+ [/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, '[REDACTED]'],
57
+ // Slack tokens
58
+ [/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, '[REDACTED]'],
59
+ // Google API key
60
+ [/\bAIza[A-Za-z0-9_-]{30,}\b/g, '[REDACTED]'],
61
+ // JWTs (three dot-separated base64url segments)
62
+ [/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, '[REDACTED]'],
63
+ // Authorization: Bearer <token>
64
+ [/\b([Bb]earer)\s+[A-Za-z0-9._-]{12,}/g, '$1 [REDACTED]'],
65
+ // key=value / "key": "value" for secret-ish key names (api_key, token,
66
+ // password, secret, passwd, authorization, access_token, private_key).
67
+ [/((?:api[_-]?key|secret|token|password|passwd|auth(?:orization)?|access[_-]?token|private[_-]?key|client[_-]?secret)["']?\s*[:=]\s*["']?)([A-Za-z0-9._/+\-]{6,})/gi, '$1[REDACTED]'],
68
+ ];
69
+ for (const [re, repl] of rules)
70
+ s = s.replace(re, repl);
71
+ return s;
72
+ }
73
+ /** Pull plain text out of a Claude/Anthropic message `content` (string or block array). */
74
+ function extractMessageText(content) {
75
+ if (content == null)
76
+ return '';
77
+ if (typeof content === 'string')
78
+ return content;
79
+ if (!Array.isArray(content))
80
+ return '';
81
+ const out = [];
82
+ for (const b of content) {
83
+ if (typeof b === 'string') {
84
+ out.push(b);
85
+ continue;
86
+ }
87
+ if (b && typeof b === 'object') {
88
+ const block = b;
89
+ if (block.type === 'text' && typeof block.text === 'string')
90
+ out.push(block.text);
91
+ }
92
+ }
93
+ return out.join('\n');
94
+ }
95
+ /** Tidy a candidate chunk: strip control chars, collapse whitespace, clip. */
96
+ function clean(text) {
97
+ return redactSecrets(text).replace(CTRL, '').replace(/[ \t]+/g, ' ').replace(/\n{3,}/g, '\n\n').trim().slice(0, 4000);
98
+ }
99
+ /** Parse a JSONL/array trace body into per-line JSON objects (tolerant). */
100
+ function parseRecords(raw) {
101
+ const trimmed = raw.trim();
102
+ if (!trimmed)
103
+ return [];
104
+ // Whole-body JSON array (dragon traces sometimes export one).
105
+ if (trimmed.startsWith('[')) {
106
+ try {
107
+ const arr = JSON.parse(trimmed);
108
+ if (Array.isArray(arr))
109
+ return arr.filter((x) => !!x && typeof x === 'object');
110
+ }
111
+ catch { /* fall through to line mode */ }
112
+ }
113
+ const out = [];
114
+ for (const line of trimmed.split('\n')) {
115
+ const t = line.trim();
116
+ if (!t || (t[0] !== '{' && t[0] !== '['))
117
+ continue;
118
+ try {
119
+ const obj = JSON.parse(t);
120
+ if (obj && typeof obj === 'object' && !Array.isArray(obj))
121
+ out.push(obj);
122
+ }
123
+ catch { /* skip non-JSON line */ }
124
+ }
125
+ return out;
126
+ }
127
+ /** Claude Code session JSONL → user/assistant prose segments. */
128
+ export function parseClaudeJsonl(raw) {
129
+ const out = [];
130
+ for (const obj of parseRecords(raw)) {
131
+ const msg = (obj.message && typeof obj.message === 'object' ? obj.message : obj);
132
+ const role = String(msg.role ?? obj.role ?? '');
133
+ if (role !== 'user' && role !== 'assistant')
134
+ continue;
135
+ const text = extractMessageText(msg.content).trim();
136
+ if (!text)
137
+ continue;
138
+ // Drop tool-result / system-reminder blocks (same rule as the capture hook).
139
+ if (role === 'user' && (text.startsWith('<') || text.startsWith('[{')))
140
+ continue;
141
+ const cleaned = clean(text);
142
+ if (cleaned.length >= 12)
143
+ out.push({ role, text: cleaned });
144
+ }
145
+ return out;
146
+ }
147
+ /** dragon-cli flywheel trace → prose segments (prompt/response/text/content). */
148
+ export function parseDragonTrace(raw) {
149
+ const out = [];
150
+ const fields = [['prompt', 'user'], ['input', 'user'], ['response', 'assistant'], ['output', 'assistant'], ['text', 'note'], ['content', 'note']];
151
+ for (const rec of parseRecords(raw)) {
152
+ for (const [field, role] of fields) {
153
+ const v = rec[field];
154
+ const text = typeof v === 'string' ? v : extractMessageText(v);
155
+ if (!text)
156
+ continue;
157
+ const cleaned = clean(text);
158
+ if (cleaned.length >= 12)
159
+ out.push({ role, text: cleaned });
160
+ }
161
+ }
162
+ return out;
163
+ }
164
+ /**
165
+ * WYRM_TRACE_TOOL_CALLS output → ONE deterministic activity-summary segment.
166
+ *
167
+ * Accepts both the Live Memory `tool_call` event shape ({ kind:'tool_call',
168
+ * actor:<toolName>, payload }) and the raw PostToolUse stdin shape
169
+ * ({ tool_name, tool_input }). Payloads are collapsed to argument KEYS only
170
+ * (never values) so a secret passed as a tool argument is never stored. The
171
+ * order of first appearance is preserved (deterministic); counts are summed.
172
+ */
173
+ export function parseToolCalls(raw) {
174
+ const counts = new Map();
175
+ const order = [];
176
+ for (const rec of parseRecords(raw)) {
177
+ const kind = String(rec.kind ?? '');
178
+ let tool = '';
179
+ if (kind === 'tool_call')
180
+ tool = String(rec.actor ?? rec.tool ?? '');
181
+ else if (typeof rec.tool_name === 'string')
182
+ tool = rec.tool_name;
183
+ else if (typeof rec.tool === 'string')
184
+ tool = rec.tool;
185
+ tool = tool.replace(CTRL, '').trim().slice(0, 80);
186
+ if (!tool)
187
+ continue;
188
+ if (!counts.has(tool))
189
+ order.push(tool);
190
+ counts.set(tool, (counts.get(tool) ?? 0) + 1);
191
+ }
192
+ if (order.length === 0)
193
+ return [];
194
+ const summary = order.map((t) => `${t}×${counts.get(t)}`).join(', ');
195
+ return [{ role: 'tool', text: `Tool activity in this run: ${summary}.` }];
196
+ }
197
+ /** Sniff the trace format from the content (best-effort, deterministic). */
198
+ export function detectTraceFormat(raw) {
199
+ const recs = parseRecords(raw).slice(0, 50);
200
+ if (recs.length === 0)
201
+ return 'claude-jsonl';
202
+ let toolish = 0, claudeish = 0;
203
+ for (const r of recs) {
204
+ if (r.kind === 'tool_call' || typeof r.tool_name === 'string')
205
+ toolish++;
206
+ const msg = (r.message && typeof r.message === 'object' ? r.message : r);
207
+ const role = msg.role ?? r.role;
208
+ if (role === 'user' || role === 'assistant' || role === 'system')
209
+ claudeish++;
210
+ }
211
+ if (toolish > 0 && toolish >= claudeish)
212
+ return 'tool-calls';
213
+ if (claudeish > 0)
214
+ return 'claude-jsonl';
215
+ return 'dragon';
216
+ }
217
+ /** Parse a trace body into normalized segments using the chosen (or sniffed) format. */
218
+ export function parseTrace(raw, format = 'auto') {
219
+ const fmt = format === 'auto' ? detectTraceFormat(raw) : format;
220
+ const segments = fmt === 'tool-calls' ? parseToolCalls(raw) :
221
+ fmt === 'dragon' ? parseDragonTrace(raw) :
222
+ parseClaudeJsonl(raw);
223
+ return { segments, format: fmt };
224
+ }
225
+ /**
226
+ * Fold parsed segments into ONE prose blob for the extractor. Deterministic
227
+ * (preserves segment order); bounded so a huge transcript can't materialize an
228
+ * unbounded segment array downstream (the deterministic extractor splits the
229
+ * FULL text). Caps total chars; drops the tail past the cap rather than
230
+ * silently mangling.
231
+ */
232
+ export function segmentsToText(segments, maxChars = 24_000) {
233
+ const parts = [];
234
+ let total = 0;
235
+ for (const s of segments) {
236
+ const line = s.text;
237
+ if (total + line.length > maxChars)
238
+ break;
239
+ parts.push(line);
240
+ total += line.length + 1;
241
+ }
242
+ return parts.join('\n');
243
+ }
244
+ //# sourceMappingURL=trace-harvest.js.map
package/dist/types.js CHANGED
@@ -1 +1,30 @@
1
- const e={database:{wal:!0,cacheSize:64e3},encryption:{enabled:!1},logging:{level:"info",console:!0},http:{port:3333,host:"127.0.0.1"},sync:{autoImport:!0,autoExport:!1}};export{e as DEFAULT_CONFIG};
1
+ /**
2
+ * Wyrm Types - Core type definitions
3
+ *
4
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd.
5
+ * @license Proprietary — (c) 2026 Ghost Protocol (Pvt) Ltd. All rights reserved. See LICENSE.
6
+ * @module types
7
+ * @version 3.0.0
8
+ */
9
+ export const DEFAULT_CONFIG = {
10
+ database: {
11
+ wal: true,
12
+ cacheSize: 64000,
13
+ },
14
+ encryption: {
15
+ enabled: false,
16
+ },
17
+ logging: {
18
+ level: 'info',
19
+ console: true,
20
+ },
21
+ http: {
22
+ port: 3333,
23
+ host: '127.0.0.1',
24
+ },
25
+ sync: {
26
+ autoImport: true,
27
+ autoExport: false,
28
+ },
29
+ };
30
+ //# sourceMappingURL=types.js.map
@@ -1,4 +1,11 @@
1
- function e(){return`<!DOCTYPE html>
1
+ /**
2
+ * Wyrm Visual Web Dashboard — Self-contained HTML SPA
3
+ *
4
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd.
5
+ * @license Proprietary — (c) 2026 Ghost Protocol (Pvt) Ltd. All rights reserved. See LICENSE.
6
+ */
7
+ export function getUIDashboardHTML() {
8
+ return `<!DOCTYPE html>
2
9
  <html lang="en">
3
10
  <head>
4
11
  <meta charset="UTF-8">
@@ -254,7 +261,7 @@ main { padding: calc(var(--nav-h) + 24px) 24px 24px; max-width: 1200px; margin:
254
261
  (function() {
255
262
  'use strict';
256
263
 
257
- // \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
264
+ // ── Helpers ─────────────────────────────────────────────────────────────────
258
265
  function el(tag, cls, text) {
259
266
  const e = document.createElement(tag);
260
267
  if (cls) e.className = cls;
@@ -306,7 +313,7 @@ async function apiPost(path) {
306
313
 
307
314
  function truncate(s, n) {
308
315
  if (!s) return '';
309
- return s.length > n ? s.slice(0, n) + '\u2026' : s;
316
+ return s.length > n ? s.slice(0, n) + '' : s;
310
317
  }
311
318
 
312
319
  function clearEl(e) {
@@ -374,7 +381,7 @@ function staleBar(staleness) {
374
381
  return wrap;
375
382
  }
376
383
 
377
- // \u2500\u2500 Router \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
384
+ // ── Router ───────────────────────────────────────────────────────────────────
378
385
  const TABS = ['overview', 'impact', 'memories', 'quests', 'truths', 'skills', 'review'];
379
386
  let currentTab = '';
380
387
  // Set from GET /ui/account.readonly at boot (WYRM_UI_READONLY=1 on the server).
@@ -406,7 +413,7 @@ document.querySelectorAll('.tab-btn').forEach(btn => {
406
413
  });
407
414
  window.addEventListener('hashchange', router);
408
415
 
409
- // \u2500\u2500 Overview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
416
+ // ── Overview ─────────────────────────────────────────────────────────────────
410
417
  async function loadOverview() {
411
418
  const pane = document.getElementById('pane-overview');
412
419
  clearEl(pane);
@@ -449,13 +456,13 @@ async function loadOverview() {
449
456
  card.replaceChild(vEl, skel);
450
457
  }
451
458
  });
452
- // Recent sessions \u2014 remove skeleton list, then insert real data
459
+ // Recent sessions remove skeleton list, then insert real data
453
460
  pane.querySelector('.list')?.remove();
454
461
  const sl = el('div', 'sessions-list');
455
462
  const sessions = data.recent_sessions || [];
456
463
  if (sessions.length === 0) {
457
464
  const em = el('div', 'empty');
458
- em.appendChild(el('div', 'empty-icon', '\u{1F4ED}'));
465
+ em.appendChild(el('div', 'empty-icon', '📭'));
459
466
  em.appendChild(el('div', null, 'No sessions yet'));
460
467
  sl.appendChild(em);
461
468
  } else {
@@ -470,7 +477,7 @@ async function loadOverview() {
470
477
  } catch(e) { showToast(String(e), 'error'); }
471
478
  }
472
479
 
473
- // \u2500\u2500 Memories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
480
+ // ── Memories ─────────────────────────────────────────────────────────────────
474
481
  let memPage = 1, memKind = '', memSearch = '', memDebounce = null;
475
482
 
476
483
  async function loadMemories(page, kind, search) {
@@ -496,7 +503,7 @@ async function loadMemories(page, kind, search) {
496
503
  const newList = el('div', 'list');
497
504
  if (!data.items || data.items.length === 0) {
498
505
  const em = el('div', 'empty');
499
- em.appendChild(el('div', 'empty-icon', '\u{1F9E0}'));
506
+ em.appendChild(el('div', 'empty-icon', '🧠'));
500
507
  em.appendChild(el('div', null, 'No memory artifacts found'));
501
508
  newList.appendChild(em);
502
509
  } else {
@@ -544,7 +551,7 @@ function initMemoriesPane() {
544
551
  const filters = el('div', 'filters');
545
552
  const si = el('input', 'filter-input');
546
553
  si.type = 'search';
547
- si.placeholder = 'Search memories\u2026';
554
+ si.placeholder = 'Search memories';
548
555
  si.addEventListener('input', () => {
549
556
  memSearch = si.value;
550
557
  clearTimeout(memDebounce);
@@ -571,7 +578,7 @@ function initMemoriesPane() {
571
578
  pane.insertBefore(filters, pane.firstChild);
572
579
  }
573
580
 
574
- // \u2500\u2500 Quests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
581
+ // ── Quests ───────────────────────────────────────────────────────────────────
575
582
  async function loadQuests() {
576
583
  const pane = document.getElementById('pane-quests');
577
584
  clearEl(pane);
@@ -583,7 +590,7 @@ async function loadQuests() {
583
590
  const col = el('div', 'kanban-col');
584
591
  const hdr = el('div', 'kanban-header');
585
592
  hdr.appendChild(el('span', 'kanban-title', colLabels[status]));
586
- const cnt = el('span', 'kanban-count', '\u2026');
593
+ const cnt = el('span', 'kanban-count', '');
587
594
  hdr.appendChild(cnt);
588
595
  col.appendChild(hdr);
589
596
  col.dataset.status = status;
@@ -620,7 +627,7 @@ async function loadQuests() {
620
627
  } catch(e) { showToast(String(e), 'error'); }
621
628
  }
622
629
 
623
- // \u2500\u2500 Truths \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
630
+ // ── Truths ───────────────────────────────────────────────────────────────────
624
631
  let truthPage = 1;
625
632
 
626
633
  async function loadTruths(page) {
@@ -639,7 +646,7 @@ async function loadTruths(page) {
639
646
  const list = el('div', 'list');
640
647
  if (!data.items || data.items.length === 0) {
641
648
  const em = el('div', 'empty');
642
- em.appendChild(el('div', 'empty-icon', '\u{1F4DA}'));
649
+ em.appendChild(el('div', 'empty-icon', '📚'));
643
650
  em.appendChild(el('div', null, 'No ground truths yet'));
644
651
  list.appendChild(em);
645
652
  } else {
@@ -649,7 +656,7 @@ async function loadTruths(page) {
649
656
  row1.appendChild(el('span', 'truth-cat', t.category));
650
657
  row1.appendChild(el('span', 'truth-key', t.key));
651
658
  if (t.staleness != null && t.staleness > 0.7) {
652
- row1.appendChild(el('span', null, '\u26A0\uFE0F'));
659
+ row1.appendChild(el('span', null, '⚠️'));
653
660
  }
654
661
  item.appendChild(row1);
655
662
  item.appendChild(el('div', 'truth-value', truncate(t.value, 150)));
@@ -682,7 +689,7 @@ async function loadTruths(page) {
682
689
  } catch(e) { showToast(String(e), 'error'); }
683
690
  }
684
691
 
685
- // \u2500\u2500 Review \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
692
+ // ── Review ───────────────────────────────────────────────────────────────────
686
693
  async function loadReview() {
687
694
  const pane = document.getElementById('pane-review');
688
695
  clearEl(pane);
@@ -694,7 +701,7 @@ async function loadReview() {
694
701
  const items = data.items || [];
695
702
  if (items.length === 0) {
696
703
  const em = el('div', 'empty');
697
- em.appendChild(el('div', 'empty-icon', '\u2705'));
704
+ em.appendChild(el('div', 'empty-icon', ''));
698
705
  em.appendChild(el('div', null, 'Review queue is empty'));
699
706
  pane.appendChild(em);
700
707
  return;
@@ -712,8 +719,8 @@ async function loadReview() {
712
719
  // Approve/reject mutate the queue, withheld in read-only/public view.
713
720
  if (!READONLY) {
714
721
  const actions = el('div', 'review-actions');
715
- const approveBtn = el('button', 'btn btn-approve', '\u2713 Approve');
716
- const rejectBtn = el('button', 'btn btn-reject', '\u2715 Reject');
722
+ const approveBtn = el('button', 'btn btn-approve', ' Approve');
723
+ const rejectBtn = el('button', 'btn btn-reject', ' Reject');
717
724
  approveBtn.addEventListener('click', async () => {
718
725
  try {
719
726
  await apiPost('/ui/review/' + m.id + '/approve');
@@ -722,7 +729,7 @@ async function loadReview() {
722
729
  if (list.querySelectorAll('.review-item').length === 0) {
723
730
  list.remove();
724
731
  const em = el('div', 'empty');
725
- em.appendChild(el('div', 'empty-icon', '\u2705'));
732
+ em.appendChild(el('div', 'empty-icon', ''));
726
733
  em.appendChild(el('div', null, 'Review queue is empty'));
727
734
  pane.appendChild(em);
728
735
  }
@@ -736,7 +743,7 @@ async function loadReview() {
736
743
  if (list.querySelectorAll('.review-item').length === 0) {
737
744
  list.remove();
738
745
  const em = el('div', 'empty');
739
- em.appendChild(el('div', 'empty-icon', '\u2705'));
746
+ em.appendChild(el('div', 'empty-icon', ''));
740
747
  em.appendChild(el('div', null, 'Review queue is empty'));
741
748
  pane.appendChild(em);
742
749
  }
@@ -752,7 +759,7 @@ async function loadReview() {
752
759
  } catch(e) { showToast(String(e), 'error'); }
753
760
  }
754
761
 
755
- // \u2500\u2500 Skills \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
762
+ // ── Skills ─────────────────────────────────────────────────────────────────
756
763
  async function loadSkills() {
757
764
  const pane = document.getElementById('pane-skills');
758
765
  clearEl(pane);
@@ -763,7 +770,7 @@ async function loadSkills() {
763
770
  const items = data.items || [];
764
771
  if (items.length === 0) {
765
772
  const em = el('div', 'empty');
766
- em.appendChild(el('div', 'empty-icon', '\u{1F9E9}'));
773
+ em.appendChild(el('div', 'empty-icon', '🧩'));
767
774
  em.appendChild(el('div', null, 'No skills registered yet'));
768
775
  pane.appendChild(em);
769
776
  return;
@@ -791,7 +798,7 @@ async function loadSkills() {
791
798
  } catch(e) { showToast(String(e), 'error'); }
792
799
  }
793
800
 
794
- // \u2500\u2500 Impact \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
801
+ // ── Impact ───────────────────────────────────────────────────────────────────
795
802
  function fmtNum(n) {
796
803
  if (n >= 1000) return (n / 1000).toFixed(n >= 10000 ? 0 : 1).replace(/\\.0$/, '') + 'k';
797
804
  return String(n || 0);
@@ -850,15 +857,15 @@ async function loadImpact() {
850
857
  } catch(e) { showToast(String(e), 'error'); }
851
858
  }
852
859
 
853
- // \u2500\u2500 Memory detail modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
860
+ // ── Memory detail modal ───────────────────────────────────────────────────────
854
861
  let modalReq = 0;
855
862
  async function openMemoryModal(id) {
856
863
  const myReq = ++modalReq;
857
864
  const ov = document.getElementById('modal-overlay');
858
865
  const card = document.getElementById('modal-card');
859
866
  clearEl(card);
860
- const close = el('span', 'modal-close', '\u2715'); close.onclick = closeModal; card.appendChild(close);
861
- card.appendChild(el('div', 'item-sub', 'Loading\u2026'));
867
+ const close = el('span', 'modal-close', ''); close.onclick = closeModal; card.appendChild(close);
868
+ card.appendChild(el('div', 'item-sub', 'Loading'));
862
869
  ov.classList.add('open');
863
870
  try {
864
871
  const d = await apiFetch('/ui/memory?id=' + id);
@@ -866,7 +873,7 @@ async function openMemoryModal(id) {
866
873
  if (myReq !== modalReq || !ov.classList.contains('open')) return;
867
874
  const it = d.item;
868
875
  clearEl(card);
869
- const cl = el('span', 'modal-close', '\u2715'); cl.onclick = closeModal; card.appendChild(cl);
876
+ const cl = el('span', 'modal-close', ''); cl.onclick = closeModal; card.appendChild(cl);
870
877
  if (!it) { card.appendChild(el('div', 'item-sub', 'Memory not found.')); return; }
871
878
  const row = el('div', 'item-row');
872
879
  if (it.kind) row.appendChild(kindBadge(it.kind));
@@ -895,12 +902,12 @@ async function openMemoryModal(id) {
895
902
  const used = (it.access_count || 0), reused = (it.reuse_count || 0);
896
903
  // "Surfaced" = times recalled (auto); "Confirmed helpful" = times an agent
897
904
  // called wyrm_feedback after applying it. 0 here means no feedback yet, not broken.
898
- card.appendChild(el('div', 'item-sub', 'Surfaced ' + used + '\xD7 \xB7 confirmed helpful ' + reused + '\xD7'));
905
+ card.appendChild(el('div', 'item-sub', 'Surfaced ' + used + '× · confirmed helpful ' + reused + '×'));
899
906
  } catch(e) { if (myReq === modalReq) closeModal(); }
900
907
  }
901
908
  function closeModal() { const ov = document.getElementById('modal-overlay'); if (ov) ov.classList.remove('open'); }
902
909
 
903
- // \u2500\u2500 Account badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
910
+ // ── Account badge ────────────────────────────────────────────────────────────
904
911
  async function loadAccount() {
905
912
  const box = document.getElementById('acct');
906
913
  if (!box) return;
@@ -920,14 +927,14 @@ async function loadAccount() {
920
927
  box.classList.add('ro');
921
928
  box.title = 'Read-only public view';
922
929
  } else {
923
- box.appendChild(el('span', 'acct-caret', '\u25BE'));
930
+ box.appendChild(el('span', 'acct-caret', ''));
924
931
  box.title = 'Click to switch account / home';
925
932
  box.onclick = toggleAcctMenu;
926
933
  }
927
934
  } catch(e) { /* badge stays empty on failure */ }
928
935
  }
929
936
 
930
- // \u2500\u2500 Account switcher dropdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
937
+ // ── Account switcher dropdown ─────────────────────────────────────────────────
931
938
  let acctMenuOpen = false;
932
939
  async function toggleAcctMenu(ev) {
933
940
  if (ev) ev.stopPropagation();
@@ -936,7 +943,7 @@ async function toggleAcctMenu(ev) {
936
943
  if (acctMenuOpen) { menu.classList.remove('open'); acctMenuOpen = false; return; }
937
944
  clearEl(menu);
938
945
  menu.appendChild(el('div', 'acct-menu-label', 'Switch account / home'));
939
- menu.appendChild(el('div', 'acct-menu-label', 'Loading\u2026'));
946
+ menu.appendChild(el('div', 'acct-menu-label', 'Loading'));
940
947
  menu.classList.add('open'); acctMenuOpen = true;
941
948
  try {
942
949
  const r = await fetch('/ui/homes');
@@ -949,9 +956,9 @@ async function toggleAcctMenu(ev) {
949
956
  const it = el('div', 'acct-menu-item' + (h.active ? ' active' : ''));
950
957
  const a = el('div', 'ai-acct');
951
958
  a.appendChild(el('span', null, h.account || h.name));
952
- if (h.active) a.appendChild(el('span', 'ai-check', '\u25CF active'));
959
+ if (h.active) a.appendChild(el('span', 'ai-check', ' active'));
953
960
  it.appendChild(a);
954
- it.appendChild(el('div', 'ai-meta', (h.tier || 'free') + ' \xB7 ' + h.name));
961
+ it.appendChild(el('div', 'ai-meta', (h.tier || 'free') + ' · ' + h.name));
955
962
  if (!h.active) it.addEventListener('click', () => switchHome(h.dbPath));
956
963
  menu.appendChild(it);
957
964
  });
@@ -983,22 +990,22 @@ document.getElementById('modal-overlay').addEventListener('click', (e) => {
983
990
  });
984
991
  document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); });
985
992
 
986
- // \u2500\u2500 Pagination helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
993
+ // ── Pagination helper ─────────────────────────────────────────────────────────
987
994
  function buildPagination(current, total, onPage) {
988
995
  const pgn = el('div', 'pagination');
989
- const prev = el('button', 'page-btn', '\u2190 Prev');
996
+ const prev = el('button', 'page-btn', ' Prev');
990
997
  prev.disabled = current <= 1;
991
998
  prev.addEventListener('click', () => onPage(current - 1));
992
999
  pgn.appendChild(prev);
993
1000
  pgn.appendChild(el('span', 'page-info', 'Page ' + current + ' of ' + total));
994
- const next = el('button', 'page-btn', 'Next \u2192');
1001
+ const next = el('button', 'page-btn', 'Next ');
995
1002
  next.disabled = current >= total;
996
1003
  next.addEventListener('click', () => onPage(current + 1));
997
1004
  pgn.appendChild(next);
998
1005
  return pgn;
999
1006
  }
1000
1007
 
1001
- // \u2500\u2500 Tab loader \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1008
+ // ── Tab loader ────────────────────────────────────────────────────────────────
1002
1009
  function loadTab(name) {
1003
1010
  if (name === 'overview') loadOverview();
1004
1011
  else if (name === 'impact') loadImpact();
@@ -1014,6 +1021,8 @@ function loadTab(name) {
1014
1021
  // so the Review pane knows whether to show approve/reject controls.
1015
1022
  loadAccount().then(router);
1016
1023
  })();
1017
- <\/script>
1024
+ </script>
1018
1025
  </body>
1019
- </html>`}export{e as getUIDashboardHTML};
1026
+ </html>`;
1027
+ }
1028
+ //# sourceMappingURL=ui-dashboard.js.map