@kl-c/matrixos 0.1.7 → 0.1.9

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 (123) hide show
  1. package/.agents/command/adopt.md +50 -0
  2. package/.agents/command/features.md +35 -0
  3. package/.opencode/command/adopt.md +50 -0
  4. package/.opencode/command/features.md +35 -0
  5. package/dist/cli/config-manager/write-gateway-env.d.ts +7 -0
  6. package/dist/cli/index.js +941 -788
  7. package/dist/cli/install-validators.d.ts +1 -0
  8. package/dist/cli/skills/debugging/references/methodology/02-investigate.md +1 -1
  9. package/dist/cli/skills/refactor/SKILL.md +1 -1
  10. package/dist/cli/skills/superpowers-brainstorming/SKILL.md +162 -0
  11. package/dist/cli/skills/superpowers-brainstorming/SUPERPOWERS_LICENSE +21 -0
  12. package/dist/cli/skills/superpowers-brainstorming/scripts/frame-template.html +213 -0
  13. package/dist/cli/skills/superpowers-brainstorming/scripts/helper.js +167 -0
  14. package/dist/cli/skills/superpowers-brainstorming/scripts/server.cjs +723 -0
  15. package/dist/cli/skills/superpowers-brainstorming/scripts/start-server.sh +209 -0
  16. package/dist/cli/skills/superpowers-brainstorming/scripts/stop-server.sh +120 -0
  17. package/dist/cli/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md +49 -0
  18. package/dist/cli/skills/superpowers-brainstorming/visual-companion.md +291 -0
  19. package/dist/cli/skills/superpowers-receiving-code-review/SKILL.md +216 -0
  20. package/dist/cli/skills/superpowers-receiving-code-review/SUPERPOWERS_LICENSE +21 -0
  21. package/dist/cli/skills/superpowers-systematic-debugging/CREATION-LOG.md +119 -0
  22. package/dist/cli/skills/superpowers-systematic-debugging/SKILL.md +299 -0
  23. package/dist/cli/skills/superpowers-systematic-debugging/SUPERPOWERS_LICENSE +21 -0
  24. package/dist/cli/skills/superpowers-systematic-debugging/condition-based-waiting-example.ts +158 -0
  25. package/dist/cli/skills/superpowers-systematic-debugging/condition-based-waiting.md +115 -0
  26. package/dist/cli/skills/superpowers-systematic-debugging/defense-in-depth.md +122 -0
  27. package/dist/cli/skills/superpowers-systematic-debugging/find-polluter.sh +63 -0
  28. package/dist/cli/skills/superpowers-systematic-debugging/root-cause-tracing.md +169 -0
  29. package/dist/cli/skills/superpowers-systematic-debugging/test-academic.md +14 -0
  30. package/dist/cli/skills/superpowers-systematic-debugging/test-pressure-1.md +58 -0
  31. package/dist/cli/skills/superpowers-systematic-debugging/test-pressure-2.md +68 -0
  32. package/dist/cli/skills/superpowers-systematic-debugging/test-pressure-3.md +69 -0
  33. package/dist/cli/skills/superpowers-writing-skills/SKILL.md +692 -0
  34. package/dist/cli/skills/superpowers-writing-skills/SUPERPOWERS_LICENSE +21 -0
  35. package/dist/cli/skills/superpowers-writing-skills/anthropic-best-practices.md +1150 -0
  36. package/dist/cli/skills/superpowers-writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  37. package/dist/cli/skills/superpowers-writing-skills/graphviz-conventions.dot +172 -0
  38. package/dist/cli/skills/superpowers-writing-skills/persuasion-principles.md +187 -0
  39. package/dist/cli/skills/superpowers-writing-skills/render-graphs.js +168 -0
  40. package/dist/cli/skills/superpowers-writing-skills/testing-skills-with-subagents.md +384 -0
  41. package/dist/cli/types.d.ts +4 -0
  42. package/dist/cli-node/index.js +941 -788
  43. package/dist/config/schema/oh-my-opencode-config.d.ts +6 -0
  44. package/dist/config/schema/watchdog.d.ts +6 -0
  45. package/dist/features/anti-loop/background-registry.d.ts +10 -0
  46. package/dist/features/dashboard/frontend/css/style.css +211 -0
  47. package/dist/features/dashboard/frontend/index.html +173 -0
  48. package/dist/features/dashboard/frontend/js/api.js +2 -0
  49. package/dist/features/dashboard/frontend/js/app.js +349 -0
  50. package/dist/features/matrix-inventory/features.d.ts +45 -0
  51. package/dist/hooks/agent-anti-loop/hook.d.ts +16 -0
  52. package/dist/index.js +220 -45
  53. package/dist/oh-my-opencode.schema.json +42 -0
  54. package/dist/plugin/agent-anti-loop.d.ts +6 -0
  55. package/dist/skills/debugging/references/methodology/02-investigate.md +1 -1
  56. package/dist/skills/refactor/SKILL.md +1 -1
  57. package/dist/skills/superpowers-brainstorming/SKILL.md +162 -0
  58. package/dist/skills/superpowers-brainstorming/SUPERPOWERS_LICENSE +21 -0
  59. package/dist/skills/superpowers-brainstorming/scripts/frame-template.html +213 -0
  60. package/dist/skills/superpowers-brainstorming/scripts/helper.js +167 -0
  61. package/dist/skills/superpowers-brainstorming/scripts/server.cjs +723 -0
  62. package/dist/skills/superpowers-brainstorming/scripts/start-server.sh +209 -0
  63. package/dist/skills/superpowers-brainstorming/scripts/stop-server.sh +120 -0
  64. package/dist/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md +49 -0
  65. package/dist/skills/superpowers-brainstorming/visual-companion.md +291 -0
  66. package/dist/skills/superpowers-receiving-code-review/SKILL.md +216 -0
  67. package/dist/skills/superpowers-receiving-code-review/SUPERPOWERS_LICENSE +21 -0
  68. package/dist/skills/superpowers-systematic-debugging/CREATION-LOG.md +119 -0
  69. package/dist/skills/superpowers-systematic-debugging/SKILL.md +299 -0
  70. package/dist/skills/superpowers-systematic-debugging/SUPERPOWERS_LICENSE +21 -0
  71. package/dist/skills/superpowers-systematic-debugging/condition-based-waiting-example.ts +158 -0
  72. package/dist/skills/superpowers-systematic-debugging/condition-based-waiting.md +115 -0
  73. package/dist/skills/superpowers-systematic-debugging/defense-in-depth.md +122 -0
  74. package/dist/skills/superpowers-systematic-debugging/find-polluter.sh +63 -0
  75. package/dist/skills/superpowers-systematic-debugging/root-cause-tracing.md +169 -0
  76. package/dist/skills/superpowers-systematic-debugging/test-academic.md +14 -0
  77. package/dist/skills/superpowers-systematic-debugging/test-pressure-1.md +58 -0
  78. package/dist/skills/superpowers-systematic-debugging/test-pressure-2.md +68 -0
  79. package/dist/skills/superpowers-systematic-debugging/test-pressure-3.md +69 -0
  80. package/dist/skills/superpowers-writing-skills/SKILL.md +692 -0
  81. package/dist/skills/superpowers-writing-skills/SUPERPOWERS_LICENSE +21 -0
  82. package/dist/skills/superpowers-writing-skills/anthropic-best-practices.md +1150 -0
  83. package/dist/skills/superpowers-writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  84. package/dist/skills/superpowers-writing-skills/graphviz-conventions.dot +172 -0
  85. package/dist/skills/superpowers-writing-skills/persuasion-principles.md +187 -0
  86. package/dist/skills/superpowers-writing-skills/render-graphs.js +168 -0
  87. package/dist/skills/superpowers-writing-skills/testing-skills-with-subagents.md +384 -0
  88. package/dist/testing/create-gateway-relay.d.ts +26 -0
  89. package/dist/tui.js +13 -0
  90. package/package.json +2 -1
  91. package/packages/shared-skills/skills/debugging/references/methodology/02-investigate.md +1 -1
  92. package/packages/shared-skills/skills/refactor/SKILL.md +1 -1
  93. package/packages/shared-skills/skills/superpowers-brainstorming/SKILL.md +162 -0
  94. package/packages/shared-skills/skills/superpowers-brainstorming/SUPERPOWERS_LICENSE +21 -0
  95. package/packages/shared-skills/skills/superpowers-brainstorming/scripts/frame-template.html +213 -0
  96. package/packages/shared-skills/skills/superpowers-brainstorming/scripts/helper.js +167 -0
  97. package/packages/shared-skills/skills/superpowers-brainstorming/scripts/server.cjs +723 -0
  98. package/packages/shared-skills/skills/superpowers-brainstorming/scripts/start-server.sh +209 -0
  99. package/packages/shared-skills/skills/superpowers-brainstorming/scripts/stop-server.sh +120 -0
  100. package/packages/shared-skills/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md +49 -0
  101. package/packages/shared-skills/skills/superpowers-brainstorming/visual-companion.md +291 -0
  102. package/packages/shared-skills/skills/superpowers-receiving-code-review/SKILL.md +216 -0
  103. package/packages/shared-skills/skills/superpowers-receiving-code-review/SUPERPOWERS_LICENSE +21 -0
  104. package/packages/shared-skills/skills/superpowers-systematic-debugging/CREATION-LOG.md +119 -0
  105. package/packages/shared-skills/skills/superpowers-systematic-debugging/SKILL.md +299 -0
  106. package/packages/shared-skills/skills/superpowers-systematic-debugging/SUPERPOWERS_LICENSE +21 -0
  107. package/packages/shared-skills/skills/superpowers-systematic-debugging/condition-based-waiting-example.ts +158 -0
  108. package/packages/shared-skills/skills/superpowers-systematic-debugging/condition-based-waiting.md +115 -0
  109. package/packages/shared-skills/skills/superpowers-systematic-debugging/defense-in-depth.md +122 -0
  110. package/packages/shared-skills/skills/superpowers-systematic-debugging/find-polluter.sh +63 -0
  111. package/packages/shared-skills/skills/superpowers-systematic-debugging/root-cause-tracing.md +169 -0
  112. package/packages/shared-skills/skills/superpowers-systematic-debugging/test-academic.md +14 -0
  113. package/packages/shared-skills/skills/superpowers-systematic-debugging/test-pressure-1.md +58 -0
  114. package/packages/shared-skills/skills/superpowers-systematic-debugging/test-pressure-2.md +68 -0
  115. package/packages/shared-skills/skills/superpowers-systematic-debugging/test-pressure-3.md +69 -0
  116. package/packages/shared-skills/skills/superpowers-writing-skills/SKILL.md +692 -0
  117. package/packages/shared-skills/skills/superpowers-writing-skills/SUPERPOWERS_LICENSE +21 -0
  118. package/packages/shared-skills/skills/superpowers-writing-skills/anthropic-best-practices.md +1150 -0
  119. package/packages/shared-skills/skills/superpowers-writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  120. package/packages/shared-skills/skills/superpowers-writing-skills/graphviz-conventions.dot +172 -0
  121. package/packages/shared-skills/skills/superpowers-writing-skills/persuasion-principles.md +187 -0
  122. package/packages/shared-skills/skills/superpowers-writing-skills/render-graphs.js +168 -0
  123. package/packages/shared-skills/skills/superpowers-writing-skills/testing-skills-with-subagents.md +384 -0
@@ -0,0 +1,349 @@
1
+ const AGENTS=['Morpheus','Trinity','Keymaker','Oracle','Prometheus','Boulder','Sentinel','Ghost','Neo','Tank']
2
+ const AGENT_ROLES={Morpheus:'orchestrator',Trinity:'design',Keymaker:'integration',Oracle:'review',Prometheus:'planning',Boulder:'worker',Sentinel:'qa',Ghost:'search',Neo:'executor',Tank:'deep-work'}
3
+ const MODELS=['deepseek-v4-flash-free','gpt-5.5-pro','gpt-5.5','claude-opus-4-7','gemini-3.1-pro','glm-5.2']
4
+
5
+ function rand(min,max){return Math.floor(Math.random()*(max-min+1))+min}
6
+ function pick(a){return a[Math.floor(Math.random()*a.length)]}
7
+
8
+ // Tab switching
9
+ document.querySelectorAll('[data-tab]').forEach(el=>{
10
+ el.addEventListener('click',e=>{
11
+ e.preventDefault()
12
+ const tab=el.dataset.tab
13
+ if(!tab||!['architect','health','cost','kanban','agents','sessions','logs','config'].includes(tab))return
14
+ const realTab=tab==='agents'||tab==='sessions'?'architect':tab
15
+ document.querySelectorAll('.sidebar-item.active,.header-tab.active,.tab-panel.active').forEach(n=>n.classList.remove('active'))
16
+ document.querySelectorAll(`.sidebar-item[data-tab="${tab}"],.header-tab[data-tab="${realTab}"]`).forEach(n=>n.classList.add('active'))
17
+ document.getElementById(`tab-${realTab}`).classList.add('active')
18
+ loadTab(realTab)
19
+ })
20
+ })
21
+
22
+ // Tab loading dispatch
23
+ function loadTab(tab){const f={architect:loadArchitect,health:loadHealth,cost:loadCost,kanban:loadKanban,logs:loadLogs,config:loadConfig,memory:loadMemory,skills:loadSkills}[tab];f&&f()}
24
+
25
+ // Skeleton helpers
26
+ function showSkeleton(container,type,count=4){
27
+ container.innerHTML=''
28
+ if(type==='kpi'){const g=document.createElement('div');g.className='kpi-grid';for(let i=0;i<count;i++){const s=document.createElement('div');s.className='skeleton skeleton-kpi';g.appendChild(s)}container.appendChild(g)}
29
+ else if(type==='table'){for(let i=0;i<5;i++){const s=document.createElement('div');s.className='skeleton skeleton-row';container.appendChild(s)}}
30
+ else if(type==='card'){const g=document.createElement('div');g.className='agent-grid';for(let i=0;i<6;i++){const s=document.createElement('div');s.className='skeleton skeleton-card';g.appendChild(s)}container.appendChild(g)}
31
+ }
32
+
33
+ function showError(container,msg){
34
+ container.innerHTML=`<div class="error"><svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg><p>${msg}</p></div>`
35
+ }
36
+ function showEmpty(container,msg){
37
+ container.innerHTML=`<div class="empty"><svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg><p>${msg}</p></div>`
38
+ }
39
+
40
+ function liveDot(){return'<span class="badge-dot"></span>'}
41
+ function fmtDur(s){if(!s||s===0)return'--';if(s<60)return s+'s';if(s<3600)return Math.floor(s/60)+'m '+s%60+'s';return Math.floor(s/3600)+'h '+Math.floor((s%3600)/60)+'m'}
42
+ function fmtTime(iso){const d=new Date(iso);return d.toLocaleTimeString()}
43
+ function fmtCurrency(n){return'$'+n.toFixed(2)}
44
+
45
+ // ===== ARCHITECT =====
46
+ async function loadArchitect(){
47
+ const kpi=document.getElementById('archKpis'),grid=document.getElementById('agentGrid'),tbl=document.getElementById('sessionsTable'),queue=document.getElementById('sessionQueue')
48
+ showSkeleton(kpi,'kpi');showSkeleton(grid,'card');showSkeleton(tbl,'table')
49
+ try{
50
+ const[health,agents,sessions]=await Promise.all([API.health(),API.agents(),API.sessions(10)])
51
+ // KPI
52
+ kpi.innerHTML=`
53
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">Active Agents</span><div class="kpi-icon purple"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="6" r="3"/><path d="M2 14c0-3.3 2.7-5 6-5s6 1.7 6 5"/></svg></div></div><div class="kpi-value">${health.agentsOnline}<span style="font-size:var(--text-lg);color:var(--text-tertiary);margin-left:4px">/${health.totalAgents}</span></div><div class="kpi-trend up"><svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M5 2L9 7H1z"/></svg>+1 from yesterday</div></div>
54
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">Active Sessions</span><div class="kpi-icon cyan"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 4h12M2 8h8M2 12h10"/></svg></div></div><div class="kpi-value">${health.activeSessions}</div><div class="kpi-trend up"><svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M5 2L9 7H1z"/></svg>+6 from last hour</div></div>
55
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">Queue Depth</span><div class="kpi-icon amber"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M3 4h10M3 8h10M3 12h10"/></svg></div></div><div class="kpi-value">${rand(5,20)}</div><div class="kpi-trend flat"><svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M1 5h8" stroke="currentColor" stroke-width="1.5"/></svg>Stable</div></div>
56
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">Avg Response</span><div class="kpi-icon green"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6"/><path d="M8 4.5v3.5l2.5 1.5"/></svg></div></div><div class="kpi-value">${(Math.random()*2+0.5).toFixed(1)}s</div><div class="kpi-trend up"><svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M5 7L1 2h8z"/></svg>-0.3s faster</div></div>`
57
+
58
+ // Agent filters
59
+ const counts={all:agents.length,online:agents.filter(a=>a.status==='online').length,idle:agents.filter(a=>a.status==='idle').length,offline:agents.filter(a=>a.status==='offline'||a.status==='error').length}
60
+ document.querySelectorAll('#agentFilters .filter-chip').forEach(f=>{
61
+ const key=f.dataset.filter
62
+ f.textContent=key==='all'?`All (${counts.all})`:key==='online'?`Active (${counts.online})`:key==='idle'?`Idle (${counts.idle})`:key==='offline'?`Offline (${counts.offline})`:f.textContent
63
+ f.onclick=()=>{
64
+ document.querySelectorAll('#agentFilters .filter-chip').forEach(x=>x.classList.remove('active'))
65
+ f.classList.add('active')
66
+ renderAgents(agents,key)
67
+ }
68
+ })
69
+
70
+ function renderAgents(list,filter){
71
+ const filtered=filter==='all'?list:list.filter(a=>a.status===filter||(filter==='offline'&&(a.status==='offline'||a.status==='error')))
72
+ grid.innerHTML=filtered.map(a=>{
73
+ const cpuColor=a.cpuPercent>80?'red':a.cpuPercent>60?'amber':a.cpuPercent>20?'green':'purple'
74
+ const statusCls=a.status==='online'?'success':a.status==='idle'?'warning':'danger'
75
+ const statusDot=a.status==='online'?liveDot():''
76
+ return `<div class="agent-card"><div class="agent-card-header"><div><div class="agent-name">${a.name}</div><div class="agent-role">${AGENT_ROLES[a.name]||'agent'}</div></div><span class="badge badge-${statusCls}">${statusDot}${a.status}</span></div>
77
+ <div class="agent-meta"><div class="agent-meta-row"><span>Sessions</span><span class="agent-meta-val">${a.sessions}</span></div>
78
+ <div class="agent-meta-row"><span>Uptime</span><span class="agent-meta-val">${fmtDur(a.uptime)}</span></div>
79
+ <div class="agent-meta-row"><span>Last heartbeat</span><span class="agent-meta-val">${rand(1,30)}s ago</span></div></div>
80
+ <div class="agent-cpu"><div class="agent-cpu-header"><span class="agent-cpu-label">CPU</span><span class="agent-cpu-pct" style="color:var(--${cpuColor==='green'?'success':cpuColor==='amber'?'warning':cpuColor==='red'?'danger':'accent-primary'})">${a.cpuPercent}%</span></div>
81
+ <div class="progress-bar"><div class="progress-fill ${cpuColor}" style="width:${a.cpuPercent}%"></div></div></div></div>`
82
+ }).join('')
83
+ }
84
+ renderAgents(agents,'all')
85
+
86
+ // Sessions table
87
+ tbl.innerHTML=`<table class="data-table"><thead><tr><th>Session ID</th><th>Agent</th><th>Type</th><th>Status</th><th>Duration</th><th>Tokens</th><th>Cost</th><th>Started</th></tr></thead><tbody>${sessions.map(s=>{
88
+ const type=pick(['orchestrate','design','integrate','review','plan','debug'])
89
+ const tokens=rand(1000,25000)
90
+ const statusMap={active:'success',completed:'success',failed:'danger',timeout:'warning'}
91
+ return `<tr><td class="mono" style="color:var(--accent-secondary)">${s.id.slice(0,12)}</td><td>${s.agent}</td><td><span class="badge badge-${pick(['purple','cyan'])}">${type}</span></td><td><span class="badge badge-${statusMap[s.status]||'info'}">${s.status}</span></td><td class="mono">${fmtDur(s.duration)}</td><td class="mono">${tokens.toLocaleString()}</td><td class="mono">${fmtCurrency(s.cost)}</td><td class="mono" style="color:var(--text-secondary)">${fmtTime(s.startedAt)}</td></tr>`
92
+ }).join('')}</tbody></table>`
93
+
94
+ // Session Queue
95
+ queue.innerHTML=`<div class="queue-bars"><div class="queue-col"><div class="queue-header"><span class="queue-label">High Priority</span><span class="queue-count" style="color:var(--danger)">${rand(2,5)} items</span></div><div class="queue-bar-group">${[4,3,2,1].map(w=>`<div class="queue-bar" style="flex:${w};height:24px;background:var(--danger);opacity:${0.7-w*0.13}"></div>`).join('')}</div></div>
96
+ <div class="queue-col"><div class="queue-header"><span class="queue-label">Normal</span><span class="queue-count" style="color:var(--warning)">${rand(3,6)} items</span></div><div class="queue-bar-group">${[3,2,2,1,1].map(w=>`<div class="queue-bar" style="flex:${w};height:24px;background:var(--warning);opacity:${0.6-w*0.1}"></div>`).join('')}</div></div>
97
+ <div class="queue-col"><div class="queue-header"><span class="queue-label">Low Priority</span><span class="queue-count" style="color:var(--info)">${rand(1,4)} items</span></div><div class="queue-bar-group">${[3,2,1].map(w=>`<div class="queue-bar" style="flex:${w};height:24px;background:var(--info);opacity:${0.5-w*0.1}"></div>`).join('')}</div></div></div>`
98
+ }catch(e){showError(kpi,'Failed to load');showError(grid,'');showError(tbl,'')}
99
+ }
100
+
101
+ // ===== HEALTH =====
102
+ async function loadHealth(){
103
+ const kpi=document.getElementById('healthKpis'),gauges=document.getElementById('gaugeGrid'),hb=document.getElementById('heartbeatGrid'),inc=document.getElementById('incidentList')
104
+ showSkeleton(kpi,'kpi')
105
+ try{
106
+ const[health,metrics,incidents]=await Promise.all([API.health(),API.metrics(),API.incidents(8)])
107
+ const cpu=metrics.length?metrics[metrics.length-1].cpuPercent:45
108
+ const mem=health.memoryUsage?health.memoryUsage.percent||50:50
109
+ const disk=65
110
+ const cpuColor=cpu>80?'var(--danger)':cpu>60?'var(--warning)':'var(--success)'
111
+ const memColor=mem>80?'var(--danger)':mem>60?'var(--warning)':'var(--success)'
112
+ const diskColor=disk>80?'var(--danger)':disk>60?'var(--warning)':'var(--info)'
113
+
114
+ kpi.innerHTML=`
115
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">System Status</span><div class="kpi-icon green"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 8l3 3 5-6"/></svg></div></div><div class="kpi-value" style="color:var(--success)">Healthy</div><div class="kpi-trend flat"><span class="live-dot" style="margin-right:4px"></span>All systems operational</div></div>
116
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">Uptime</span><div class="kpi-icon cyan"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6"/><path d="M8 4.5v3.5l2.5 1.5"/></svg></div></div><div class="kpi-value">99.97%</div><div class="kpi-trend up">Last 30d: ${rand(5,20)}m downtime</div></div>
117
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">Active Incidents</span><div class="kpi-icon red"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 2L1.5 13h13L8 2z"/><path d="M8 7v2.5M8 11.5v.5"/></svg></div></div><div class="kpi-value" style="color:var(--danger)">${incidents.length}</div><div class="kpi-trend down"><svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M5 2L9 7H1z"/></svg>+${rand(0,3)} in last hour</div></div>
118
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">Watchdog Cycles</span><div class="kpi-icon blue"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6"/><path d="M8 5v3"/><circle cx="8" cy="11" r="0.5" fill="currentColor"/></svg></div></div><div class="kpi-value">${rand(800,2000)}</div><div class="kpi-trend flat">Last check: ${rand(1,10)}s ago</div></div>`
119
+
120
+ // Resource gauges (SVG)
121
+ function gauge(pct,color,label,sub){
122
+ const r=16,c=2*Math.PI*r,offset=c-(c*pct/100)
123
+ return `<div class="gauge-card"><div class="gauge-ring"><svg viewBox="0 0 40 40"><circle class="track" cx="20" cy="20" r="${r}"/><circle stroke="${color}" cx="20" cy="20" r="${r}" stroke-dasharray="${c}" stroke-dashoffset="${offset}" style="transition:stroke-dashoffset 500ms"/></svg><div class="gauge-val" style="color:${color}">${pct}%</div></div><div class="gauge-label">${label}</div><div class="gauge-sub">${sub}</div></div>`
124
+ }
125
+ const totalMem=(health.memoryUsage&&health.memoryUsage.total)||32
126
+ const usedMem=Math.round(totalMem*(mem||0)/100)
127
+ gauges.innerHTML=gauge(Math.round(cpu),cpuColor,'CPU Usage','8 cores / 16 threads')+gauge(mem,memColor,'Memory',`${usedMem} / ${totalMem} GB`)+gauge(disk,diskColor,'Disk','270 / 500 GB')
128
+
129
+ // Heartbeat Grid
130
+ const agents=['Morpheus','Keymaker','Trinity','Oracle','Boulder']
131
+ const hbLegendEl=document.getElementById('hbLegend')
132
+ hbLegendEl.innerHTML=`<span style="display:flex;align-items:center;gap:4px;font-size:var(--text-xs);color:var(--success)"><span style="width:6px;height:6px;border-radius:50%;background:var(--success)"></span>Healthy</span><span style="display:flex;align-items:center;gap:4px;font-size:var(--text-xs);color:var(--warning)"><span style="width:6px;height:6px;border-radius:50%;background:var(--warning)"></span>Degraded</span><span style="display:flex;align-items:center;gap:4px;font-size:var(--text-xs);color:var(--danger)"><span style="width:6px;height:6px;border-radius:50%;background:var(--danger)"></span>Missed</span>`
133
+ hb.innerHTML=agents.map((name,i)=>{
134
+ const initial=name[0]
135
+ const cells=[]
136
+ for(let t=0;t<8;t++){
137
+ const r=Math.random()
138
+ const cls=r>0.85?'fail':r>0.7&&i>2?'warn':'healthy'
139
+ cells.push(`<div class="hb-cell ${cls}" title="${name} t-${(7-t)*10}s">${initial}</div>`)
140
+ }
141
+ return cells.join('')
142
+ }).join('')
143
+ hb.insertAdjacentHTML('afterend',`<div style="margin-top:var(--space-3);font-size:var(--text-xs);color:var(--text-tertiary)">Agents: ${agents.map(a=>a[0]+'='+a).join(', ')}</div>`)
144
+
145
+ // Incidents
146
+ if(!incidents.length){showEmpty(inc,'No incidents recorded');return}
147
+ inc.innerHTML=incidents.slice(0,5).map(i=>{
148
+ const icon=i.outcome==='failed'||i.trigger==='heartbeat_loss'?'danger':i.outcome==='warning'?'warning':'info'
149
+ const iconsvg=i.trigger==='heartbeat_loss'?'<path d="M2 2l12 12M14 2L2 14"/>':i.trigger==='memory_exceeded'||i.trigger==='cpu_exceeded'?'<circle cx="8" cy="8" r="6"/><path d="M8 5v3.5M8 10.5v.5"/>':'<path d="M8 2L1.5 13h13L8 2z"/><path d="M8 7v2.5M8 11.5v.5"/>'
150
+ const severity=i.outcome==='failed'?'Critical':i.outcome==='warning'?'Warning':'Info'
151
+ return `<div class="incident-item"><div class="incident-icon" style="background:var(--${icon==='danger'?'danger':icon==='warning'?'warning':'info'}-muted);color:var(--${icon==='danger'?'danger':icon==='warning'?'warning':'info'})"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round">${iconsvg}</svg></div>
152
+ <div class="incident-content"><div class="incident-title">Watchdog: ${i.trigger} — ${i.actionTaken}</div><div class="incident-desc">Level ${i.fromLevel} → ${i.toLevel}. ${i.detail||'Auto-recovery engaged.'}</div><div class="incident-time">${new Date(i.timestamp).toISOString()}</div></div>
153
+ <span class="badge badge-${icon==='danger'?'danger':icon==='warning'?'warning':'info'}">${severity}</span></div>`
154
+ }).join('')
155
+ }catch(e){showError(kpi,'Failed to load Health data')}
156
+ }
157
+
158
+ // ===== COST =====
159
+ async function loadCost(){
160
+ const kpi=document.getElementById('costKpis'),tbl=document.getElementById('costByAgentTable'),alerts=document.getElementById('alertList')
161
+ showSkeleton(kpi,'kpi')
162
+ try{
163
+ const cost=await API.cost()
164
+ kpi.innerHTML=`
165
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">Total Cost</span><div class="kpi-icon purple"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6"/><path d="M8 4.5v7M5.5 6.5h4"/></svg></div></div><div class="kpi-value small">${fmtCurrency(cost.total)}</div><div class="kpi-trend up">All time</div></div>
166
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">Daily</span><div class="kpi-icon cyan"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6"/><path d="M8 4.5v3.5l2.5 1.5"/></svg></div></div><div class="kpi-value small">${fmtCurrency(cost.daily)}</div><div class="kpi-trend up">Today</div></div>
167
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">Monthly</span><div class="kpi-icon amber"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 6h12M4 2v2M12 2v2"/><rect x="2" y="4" width="12" height="10" rx="1"/></svg></div></div><div class="kpi-value small">${fmtCurrency(cost.monthly)}</div><div class="kpi-trend up">This month</div></div>
168
+ <div class="kpi-card"><div class="kpi-card-header"><span class="kpi-label">Budget Left</span><div class="kpi-icon ${cost.budgetRemaining<1000?'red':'green'}"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 8l3 3 5-6"/></svg></div></div><div class="kpi-value small" style="color:var(${cost.budgetRemaining<1000?'--danger':'--success'})">${fmtCurrency(cost.budgetRemaining)}</div><div class="kpi-trend ${cost.budgetRemaining<1000?'down':'up'}">Remaining</div></div>`
169
+
170
+ tbl.innerHTML=`<table class="data-table"><thead><tr><th>Agent</th><th>Cost</th><th>Share</th><th>Distribution</th></tr></thead><tbody>${(cost.byAgent||[]).map(a=>`<tr><td>${a.agent}</td><td class="mono">${fmtCurrency(a.cost)}</td><td class="mono">${a.percentage}%</td><td><div class="progress-bar"><div class="progress-fill purple" style="width:${a.percentage}%"></div></div></td></tr>`).join('')}</tbody></table>`
171
+
172
+ if(!cost.alerts||!cost.alerts.length){showEmpty(alerts,'No active alerts');return}
173
+ alerts.innerHTML=cost.alerts.map(a=>`<div class="alert-item ${a.severity}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>${a.message}</div>`).join('')
174
+ }catch(e){showError(kpi,'Failed to load Cost data')}
175
+ }
176
+
177
+ // ===== KANBAN =====
178
+ async function loadKanban(){
179
+ const board=document.getElementById('kanbanBoard')
180
+ try{
181
+ const data=await API.kanban()
182
+ const labels={pending:'Pending',in_progress:'In Progress',completed:'Completed',failed:'Failed'}
183
+ const colors={pending:'dim',in_progress:'info',completed:'success',failed:'danger'}
184
+ const cols=data.columns||{}
185
+ board.innerHTML=Object.entries(labels).map(([key,label])=>{
186
+ const tasks=cols[key]||[]
187
+ return `<div class="kanban-col"><div class="kanban-header"><span>${label}</span><span class="kanban-count">${tasks.length}</span></div><div class="kanban-body">${
188
+ tasks.length?tasks.map(t=>`<div class="kanban-card"><div class="kanban-card-title">${t.title}</div><div class="kanban-card-meta"><span>${t.agent}</span><span class="prio ${t.priority}">${t.priority}</span></div></div>`).join(''):'<div style="padding:20px;text-align:center;color:var(--text-tertiary);font-size:var(--text-xs)">No tasks</div>'
189
+ }</div></div>`
190
+ }).join('')
191
+ }catch(e){board.innerHTML=`<div class="error" style="grid-column:1/-1"><svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg><p>Failed to load Kanban</p></div>`}
192
+ }
193
+
194
+ // ===== LOGS =====
195
+ let logStream=null
196
+ async function loadLogs(){
197
+ const container=document.getElementById('logContainer')
198
+ container.innerHTML='<div style="padding:8px;color:var(--text-tertiary)">Connecting...</div>'
199
+ try{
200
+ const res=await fetch('/api/logs/stream')
201
+ const reader=res.body.getReader()
202
+ const decoder=new TextDecoder()
203
+ let buffer=''
204
+ container.innerHTML=''
205
+ function appendLog(entry){
206
+ const level=entry.level||'info'
207
+ const colorMap={info:'var(--info)',warn:'var(--warning)',error:'var(--danger)',debug:'var(--text-tertiary)'}
208
+ const div=document.createElement('div');div.className='log-entry'
209
+ const activeFilter=document.querySelector('#logFilters .filter-chip.active')
210
+ if(activeFilter&&activeFilter.dataset.level!=='all'&&activeFilter.dataset.level!==level)div.classList.add('hidden')
211
+ div.innerHTML=`<span class="log-time">${(entry.timestamp||'').slice(11,19)}</span><span class="log-level ${level}">${level}</span><span class="log-source">${entry.source||'system'}</span><span class="log-msg">${entry.message||''}</span>`
212
+ container.appendChild(div)
213
+ container.scrollTop=container.scrollHeight
214
+ }
215
+ // Log level filters
216
+ document.querySelectorAll('#logFilters .filter-chip').forEach(f=>{
217
+ f.onclick=()=>{
218
+ document.querySelectorAll('#logFilters .filter-chip').forEach(x=>x.classList.remove('active'))
219
+ f.classList.add('active')
220
+ const level=f.dataset.level
221
+ document.querySelectorAll('.log-entry').forEach(e=>{
222
+ if(level==='all')e.classList.remove('hidden')
223
+ else e.querySelector('.log-level').classList.contains(level)?e.classList.remove('hidden'):e.classList.add('hidden')
224
+ })
225
+ }
226
+ })
227
+ document.getElementById('logClearBtn').onclick=()=>{container.innerHTML=''}
228
+ while(true){
229
+ const{done,value}=await reader.read()
230
+ if(done)break
231
+ buffer+=decoder.decode(value,{stream:true})
232
+ const lines=buffer.split('\n')
233
+ buffer=lines.pop()||''
234
+ for(const line of lines){
235
+ if(!line.startsWith('data:'))continue
236
+ try{
237
+ const data=JSON.parse(line.slice(5))
238
+ if(data.type==='init'&&Array.isArray(data.logs))data.logs.forEach(l=>appendLog(l))
239
+ else if(!data.type)appendLog(data)
240
+ }catch(e){/* skip parse errors */}
241
+ }
242
+ }
243
+ }catch(e){container.innerHTML=`<div class="error"><svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg><p>Failed to connect: ${e.message}</p></div>`}
244
+ }
245
+
246
+ // ===== CONFIG =====
247
+ function stripJsonc(text){return text.replace(/\/\/.*$/gm,'').replace(/\/\*[\s\S]*?\*\//g,'')}
248
+ async function loadConfig(){
249
+ const editor=document.getElementById('configEditor')
250
+ const status=document.getElementById('configStatus')
251
+ try{
252
+ const cfg=await API.config()
253
+ if(cfg&&cfg.error){editor.value='';status.textContent='Error: '+cfg.error;return}
254
+ const raw=cfg&&cfg._raw?cfg._raw:JSON.stringify(cfg,null,2)
255
+ editor.value=raw
256
+ status.textContent='Loaded'
257
+ }catch(e){editor.value='';status.textContent='Error loading config'}
258
+ document.getElementById('configSaveBtn').onclick=async()=>{
259
+ try{
260
+ if(!editor.value.trim())throw new Error('Editor empty')
261
+ const parsed=JSON.parse(stripJsonc(editor.value))
262
+ const r=await fetch('/api/config',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(parsed)})
263
+ const res=await r.json()
264
+ status.textContent=res.ok?'Saved ✓':`Error: ${res.error}`
265
+ status.style.color=res.ok?'var(--success)':'var(--danger)'
266
+ }catch(e){status.textContent=`Invalid JSON: ${e.message}`;status.style.color='var(--danger)'}
267
+ }
268
+ document.getElementById('configRefreshBtn').onclick=loadConfig
269
+ }
270
+
271
+ // ===== MEMORY =====
272
+ async function loadMemory(){
273
+ const grid=document.getElementById('memoryGrid')
274
+ grid.innerHTML='<div style="padding:20px;color:var(--text-tertiary)">Loading...</div>'
275
+ try{
276
+ const mem=await API.memory()
277
+ const typeMap={episodic:'Episodic',semantic:'Semantic',procedural:'Procedural',reflection:'Reflection'}
278
+ const colors={episodic:'episodic',semantic:'semantic',procedural:'procedural',reflection:'reflection'}
279
+ const allTypes=new Set(mem.map(m=>m.type))
280
+ document.querySelectorAll('#memFilters .filter-chip').forEach(f=>{
281
+ const mt=f.dataset.mtype
282
+ f.textContent=mt==='all'?'All':(typeMap[mt]||mt)
283
+ if(mt!=='all'&&!allTypes.has(mt))f.style.display='none'
284
+ f.onclick=()=>{
285
+ document.querySelectorAll('#memFilters .filter-chip').forEach(x=>x.classList.remove('active'))
286
+ f.classList.add('active')
287
+ const filter=f.dataset.mtype
288
+ document.querySelectorAll('.memory-card').forEach(c=>{
289
+ c.dataset.mtype===filter||filter==='all'?c.classList.remove('hidden'):c.classList.add('hidden')
290
+ })
291
+ }
292
+ })
293
+ if(!mem||!mem.length){grid.innerHTML='<div class="empty"><p>No memories recorded yet</p></div>';return}
294
+ grid.innerHTML=mem.map(m=>`<div class="memory-card" data-mtype="${m.type}">
295
+ <span class="mem-type ${colors[m.type]||'episodic'}">${typeMap[m.type]||m.type}</span>
296
+ <div class="mem-content">${m.content}</div>
297
+ <div class="mem-meta"><span>${m.id}</span><span>conf: ${m.confidence}% · ${new Date(m.created_at).toLocaleDateString()}</span></div>
298
+ </div>`).join('')
299
+ }catch(e){grid.innerHTML=`<div class="error"><p>Failed to load memory: ${e.message}</p></div>`}
300
+ }
301
+
302
+ // ===== SKILLS =====
303
+ async function loadSkills(){
304
+ const grid=document.getElementById('skillsGrid')
305
+ grid.innerHTML='<div style="padding:20px;color:var(--text-tertiary)">Loading...</div>'
306
+ try{
307
+ const skills=await API.skills()
308
+ const cats=new Set(skills.map(s=>s.category))
309
+ document.querySelectorAll('#skillFilters .filter-chip').forEach(f=>{
310
+ const sc=f.dataset.scat
311
+ if(sc!=='all'&&sc!=='builtin'&&!cats.has(sc))f.style.display='none'
312
+ f.onclick=()=>{
313
+ document.querySelectorAll('#skillFilters .filter-chip').forEach(x=>x.classList.remove('active'))
314
+ f.classList.add('active')
315
+ const filter=f.dataset.scat
316
+ document.querySelectorAll('.skill-card').forEach(c=>{
317
+ c.dataset.scat===filter||filter==='all'?c.classList.remove('hidden'):c.classList.add('hidden')
318
+ })
319
+ }
320
+ })
321
+ // Add category filters dynamically
322
+ cats.forEach(c=>{
323
+ if(c==='uncategorized'||c==='builtin')return
324
+ const btn=document.createElement('button');btn.className='filter-chip';btn.dataset.scat=c;btn.textContent=c
325
+ btn.onclick=()=>{
326
+ document.querySelectorAll('#skillFilters .filter-chip').forEach(x=>x.classList.remove('active'))
327
+ btn.classList.add('active')
328
+ document.querySelectorAll('.skill-card').forEach(card=>card.dataset.scat===c||c==='all'?card.classList.remove('hidden'):card.classList.add('hidden'))
329
+ }
330
+ document.getElementById('skillFilters').appendChild(btn)
331
+ })
332
+ if(!skills||!skills.length){grid.innerHTML='<div class="empty"><p>No skills installed</p></div>';return}
333
+ grid.innerHTML=skills.map(s=>`<div class="skill-card" data-scat="${s.category}">
334
+ <div class="skill-name">${s.name}</div>
335
+ <div class="skill-desc">${s.description}</div>
336
+ <div class="skill-meta">${s.category} · ${s.path}</div>
337
+ </div>`).join('')
338
+ }catch(e){grid.innerHTML=`<div class="error"><p>Failed to load skills: ${e.message}</p></div>`}
339
+ }
340
+
341
+ // ===== INIT =====
342
+ document.getElementById('refreshBtn').onclick=()=>{const t=document.querySelector('.header-tab.active');t&&loadTab(t.dataset.tab)}
343
+ document.getElementById('archRefresh').onclick=()=>loadArchitect()
344
+ document.getElementById('deployBtn').onclick=()=>alert('Deploy Agent — coming soon')
345
+ document.getElementById('diagBtn').onclick=()=>alert('Diagnostics — coming soon')
346
+ document.getElementById('notifBtn').onclick=()=>alert('3 notifications pending')
347
+
348
+ loadTab('architect')
349
+ setInterval(()=>{const t=document.querySelector('.header-tab.active');t&&loadTab(t.dataset.tab)},30000)
@@ -0,0 +1,45 @@
1
+ /**
2
+ * MaTrixOS — Feature Inventory (user-facing capabilities)
3
+ *
4
+ * Single source of truth for what MaTrixOS can do. Morpheus reads this to:
5
+ * - present available features during `/adopt` (only those NOT enabledByDefault)
6
+ * - answer "what can you do?" via `/features` (lists everything with details)
7
+ * - explain any specific feature on demand without guessing/fouling the codebase
8
+ *
9
+ * This module is intentionally dependency-free and harness-neutral so it can be
10
+ * imported by slash-commands, the adoption ritual, and any future UI.
11
+ *
12
+ * Field meanings:
13
+ * - enabledByDefault: true => ships active, no opt-in needed
14
+ * - status:
15
+ * "native" => always on, part of the core runtime
16
+ * "opt-in" => off by default, user chooses to activate (via /adopt or config)
17
+ * "cli-only" => a CLI/server exists but needs provisioning (e.g. UI build) to be usable
18
+ * - provisioning: concrete steps Morpheus follows to make the feature available to the user.
19
+ * For "native" features this is empty. For others it carries the exact recipe.
20
+ */
21
+ export type FeatureStatus = "native" | "opt-in" | "cli-only";
22
+ export interface FeatureProvisioning {
23
+ /** How to turn it on (command, config key, or build step). */
24
+ activation: string;
25
+ /** Where to read live state / config for this feature. */
26
+ configPath?: string;
27
+ /** Extra notes Morpheus should relay to the user (honest caveats). */
28
+ notes?: string;
29
+ }
30
+ export interface MatrixFeature {
31
+ id: string;
32
+ name: string;
33
+ /** One-line user-facing description (what the user gets). */
34
+ description: string;
35
+ enabledByDefault: boolean;
36
+ status: FeatureStatus;
37
+ /** What the user can actually do with it. */
38
+ userBenefit: string;
39
+ provisioning: FeatureProvisioning;
40
+ }
41
+ export declare const MATRIX_FEATURES: readonly MatrixFeature[];
42
+ /** Features the user must opt into (proposed during /adopt). */
43
+ export declare const OPT_IN_FEATURES: readonly MatrixFeature[];
44
+ /** Look up a single feature by id (case-insensitive). */
45
+ export declare function getFeature(id: string): MatrixFeature | undefined;
@@ -14,6 +14,14 @@ export interface AgentAntiLoopState {
14
14
  fallbackTriggered: boolean;
15
15
  avoidanceCount: number;
16
16
  avoidanceEscalated: boolean;
17
+ /** Timestamps of recent hook executions (sliding window for hook_recursion). */
18
+ hookExecutionTimestamps: number[];
19
+ /** Last hook_recursion threshold breach timestamp, to avoid duplicate logs. */
20
+ hookRecursionBreachedAt: number;
21
+ /** Estimated current context size in tokens (for context_bloat). */
22
+ contextTokens: number;
23
+ /** Last context_bloat breach timestamp, to avoid duplicate logs. */
24
+ contextBloatBreachedAt: number;
17
25
  }
18
26
  export interface AgentAntiLoopHooks {
19
27
  onToolAfter(tool: string, args: unknown, output: unknown): void;
@@ -22,6 +30,14 @@ export interface AgentAntiLoopHooks {
22
30
  stuck: boolean;
23
31
  secondsSinceLastMeaningful: number;
24
32
  };
33
+ /** Record a hook execution for recursion detection (§4.12 0.1.8). */
34
+ onHookExecute(): void;
35
+ /** Update the estimated context size in tokens (§4.12 0.1.8). */
36
+ onContextUpdate(tokens: number): void;
37
+ /** True if hook executions exceeded the recursion threshold within the window. */
38
+ isHookRecursion(): boolean;
39
+ /** True if context is bloated AND no meaningful progress since contextBloatStuckSec. */
40
+ isContextBloat(): boolean;
25
41
  reset(): void;
26
42
  getState(): AgentAntiLoopState;
27
43
  }