@miller-tech/uap 2.21.2 → 2.36.1

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 (328) hide show
  1. package/README.md +22 -10
  2. package/config/model-profiles/qwen38-coder390.json +46 -0
  3. package/config/model-profiles/qwen38.json +3 -2
  4. package/config/model-registry.json +110 -0
  5. package/dist/.tsbuildinfo +1 -1
  6. package/dist/benchmarks/paired/types.d.ts +2 -2
  7. package/dist/bin/cli.js +315 -20
  8. package/dist/bin/cli.js.map +1 -1
  9. package/dist/cli/attention.d.ts +6 -0
  10. package/dist/cli/attention.d.ts.map +1 -0
  11. package/dist/cli/attention.js +115 -0
  12. package/dist/cli/attention.js.map +1 -0
  13. package/dist/cli/bench-report.d.ts +82 -0
  14. package/dist/cli/bench-report.d.ts.map +1 -0
  15. package/dist/cli/bench-report.js +237 -0
  16. package/dist/cli/bench-report.js.map +1 -0
  17. package/dist/cli/clients.d.ts +6 -0
  18. package/dist/cli/clients.d.ts.map +1 -0
  19. package/dist/cli/clients.js +142 -0
  20. package/dist/cli/clients.js.map +1 -0
  21. package/dist/cli/dashboard-grants.d.ts +26 -0
  22. package/dist/cli/dashboard-grants.d.ts.map +1 -0
  23. package/dist/cli/dashboard-grants.js +69 -0
  24. package/dist/cli/dashboard-grants.js.map +1 -0
  25. package/dist/cli/dashboard-pace.d.ts +31 -0
  26. package/dist/cli/dashboard-pace.d.ts.map +1 -0
  27. package/dist/cli/dashboard-pace.js +42 -0
  28. package/dist/cli/dashboard-pace.js.map +1 -0
  29. package/dist/cli/dashboard-session.d.ts +25 -0
  30. package/dist/cli/dashboard-session.d.ts.map +1 -0
  31. package/dist/cli/dashboard-session.js +814 -0
  32. package/dist/cli/dashboard-session.js.map +1 -0
  33. package/dist/cli/dashboard-types.d.ts +18 -0
  34. package/dist/cli/dashboard-types.d.ts.map +1 -0
  35. package/dist/cli/dashboard-types.js +9 -0
  36. package/dist/cli/dashboard-types.js.map +1 -0
  37. package/dist/cli/dashboard.d.ts +2 -18
  38. package/dist/cli/dashboard.d.ts.map +1 -1
  39. package/dist/cli/dashboard.js +43 -793
  40. package/dist/cli/dashboard.js.map +1 -1
  41. package/dist/cli/deliver.d.ts +35 -0
  42. package/dist/cli/deliver.d.ts.map +1 -1
  43. package/dist/cli/deliver.js +93 -9
  44. package/dist/cli/deliver.js.map +1 -1
  45. package/dist/cli/diff.d.ts +85 -0
  46. package/dist/cli/diff.d.ts.map +1 -0
  47. package/dist/cli/diff.js +293 -0
  48. package/dist/cli/diff.js.map +1 -0
  49. package/dist/cli/eta.d.ts +35 -0
  50. package/dist/cli/eta.d.ts.map +1 -0
  51. package/dist/cli/eta.js +101 -0
  52. package/dist/cli/eta.js.map +1 -0
  53. package/dist/cli/handsfree.d.ts +7 -0
  54. package/dist/cli/handsfree.d.ts.map +1 -1
  55. package/dist/cli/handsfree.js +27 -0
  56. package/dist/cli/handsfree.js.map +1 -1
  57. package/dist/cli/hooks.d.ts.map +1 -1
  58. package/dist/cli/hooks.js +126 -5
  59. package/dist/cli/hooks.js.map +1 -1
  60. package/dist/cli/loops.d.ts +24 -5
  61. package/dist/cli/loops.d.ts.map +1 -1
  62. package/dist/cli/loops.js +92 -12
  63. package/dist/cli/loops.js.map +1 -1
  64. package/dist/cli/models.d.ts +62 -0
  65. package/dist/cli/models.d.ts.map +1 -0
  66. package/dist/cli/models.js +567 -0
  67. package/dist/cli/models.js.map +1 -0
  68. package/dist/cli/plan.d.ts +9 -0
  69. package/dist/cli/plan.d.ts.map +1 -1
  70. package/dist/cli/plan.js +57 -4
  71. package/dist/cli/plan.js.map +1 -1
  72. package/dist/cli/proxy-relay.d.ts +55 -0
  73. package/dist/cli/proxy-relay.d.ts.map +1 -0
  74. package/dist/cli/proxy-relay.js +116 -0
  75. package/dist/cli/proxy-relay.js.map +1 -0
  76. package/dist/cli/proxy.d.ts +4 -0
  77. package/dist/cli/proxy.d.ts.map +1 -1
  78. package/dist/cli/proxy.js +26 -1
  79. package/dist/cli/proxy.js.map +1 -1
  80. package/dist/cli/sandbox.d.ts +101 -4
  81. package/dist/cli/sandbox.d.ts.map +1 -1
  82. package/dist/cli/sandbox.js +393 -44
  83. package/dist/cli/sandbox.js.map +1 -1
  84. package/dist/cli/self.d.ts +13 -0
  85. package/dist/cli/self.d.ts.map +1 -0
  86. package/dist/cli/self.js +94 -0
  87. package/dist/cli/self.js.map +1 -0
  88. package/dist/cli/systemd-services.d.ts +2 -8
  89. package/dist/cli/systemd-services.d.ts.map +1 -1
  90. package/dist/cli/systemd-services.js +5 -20
  91. package/dist/cli/systemd-services.js.map +1 -1
  92. package/dist/cli/task.js +1 -1
  93. package/dist/cli/task.js.map +1 -1
  94. package/dist/cli/wizard-config.d.ts.map +1 -1
  95. package/dist/cli/wizard-config.js +31 -2
  96. package/dist/cli/wizard-config.js.map +1 -1
  97. package/dist/config/settings-registry.d.ts +1 -1
  98. package/dist/config/settings-registry.d.ts.map +1 -1
  99. package/dist/config/settings-registry.js +108 -0
  100. package/dist/config/settings-registry.js.map +1 -1
  101. package/dist/coordination/capability-router.js +2 -2
  102. package/dist/coordination/capability-router.js.map +1 -1
  103. package/dist/coordination/database.d.ts +13 -0
  104. package/dist/coordination/database.d.ts.map +1 -1
  105. package/dist/coordination/database.js +63 -7
  106. package/dist/coordination/database.js.map +1 -1
  107. package/dist/coordination/eta-inject.d.ts +32 -0
  108. package/dist/coordination/eta-inject.d.ts.map +1 -0
  109. package/dist/coordination/eta-inject.js +64 -0
  110. package/dist/coordination/eta-inject.js.map +1 -0
  111. package/dist/coordination/reactor.d.ts.map +1 -1
  112. package/dist/coordination/reactor.js +14 -0
  113. package/dist/coordination/reactor.js.map +1 -1
  114. package/dist/coordination/service.d.ts +18 -8
  115. package/dist/coordination/service.d.ts.map +1 -1
  116. package/dist/coordination/service.js +38 -28
  117. package/dist/coordination/service.js.map +1 -1
  118. package/dist/dashboard/client-registry.d.ts +163 -0
  119. package/dist/dashboard/client-registry.d.ts.map +1 -0
  120. package/dist/dashboard/client-registry.js +549 -0
  121. package/dist/dashboard/client-registry.js.map +1 -0
  122. package/dist/dashboard/controls.d.ts.map +1 -1
  123. package/dist/dashboard/controls.js +33 -4
  124. package/dist/dashboard/controls.js.map +1 -1
  125. package/dist/dashboard/data-seeder.d.ts +10 -7
  126. package/dist/dashboard/data-seeder.d.ts.map +1 -1
  127. package/dist/dashboard/data-seeder.js +31 -75
  128. package/dist/dashboard/data-seeder.js.map +1 -1
  129. package/dist/dashboard/data-service.d.ts +9 -0
  130. package/dist/dashboard/data-service.d.ts.map +1 -1
  131. package/dist/dashboard/data-service.js +32 -3
  132. package/dist/dashboard/data-service.js.map +1 -1
  133. package/dist/dashboard/placement-routes.d.ts +119 -0
  134. package/dist/dashboard/placement-routes.d.ts.map +1 -0
  135. package/dist/dashboard/placement-routes.js +331 -0
  136. package/dist/dashboard/placement-routes.js.map +1 -0
  137. package/dist/dashboard/server.d.ts.map +1 -1
  138. package/dist/dashboard/server.js +408 -2
  139. package/dist/dashboard/server.js.map +1 -1
  140. package/dist/delivery/attention.d.ts +130 -0
  141. package/dist/delivery/attention.d.ts.map +1 -0
  142. package/dist/delivery/attention.js +341 -0
  143. package/dist/delivery/attention.js.map +1 -0
  144. package/dist/delivery/decompose.d.ts +18 -0
  145. package/dist/delivery/decompose.d.ts.map +1 -1
  146. package/dist/delivery/decompose.js +22 -1
  147. package/dist/delivery/decompose.js.map +1 -1
  148. package/dist/delivery/eta-estimate.d.ts +111 -0
  149. package/dist/delivery/eta-estimate.d.ts.map +1 -0
  150. package/dist/delivery/eta-estimate.js +171 -0
  151. package/dist/delivery/eta-estimate.js.map +1 -0
  152. package/dist/delivery/eta.d.ts +23 -0
  153. package/dist/delivery/eta.d.ts.map +1 -0
  154. package/dist/delivery/eta.js +59 -0
  155. package/dist/delivery/eta.js.map +1 -0
  156. package/dist/delivery/orchestrated-mission.d.ts +10 -0
  157. package/dist/delivery/orchestrated-mission.d.ts.map +1 -1
  158. package/dist/delivery/orchestrated-mission.js +15 -1
  159. package/dist/delivery/orchestrated-mission.js.map +1 -1
  160. package/dist/delivery/plan-check.d.ts +7 -1
  161. package/dist/delivery/plan-check.d.ts.map +1 -1
  162. package/dist/delivery/plan-check.js +23 -2
  163. package/dist/delivery/plan-check.js.map +1 -1
  164. package/dist/delivery/run-state.d.ts +1 -1
  165. package/dist/delivery/run-state.d.ts.map +1 -1
  166. package/dist/delivery/run-state.js +8 -1
  167. package/dist/delivery/run-state.js.map +1 -1
  168. package/dist/delivery/task-orchestrator.d.ts +8 -3
  169. package/dist/delivery/task-orchestrator.d.ts.map +1 -1
  170. package/dist/delivery/task-orchestrator.js +75 -43
  171. package/dist/delivery/task-orchestrator.js.map +1 -1
  172. package/dist/delivery/task-sync.d.ts +21 -0
  173. package/dist/delivery/task-sync.d.ts.map +1 -1
  174. package/dist/delivery/task-sync.js +44 -8
  175. package/dist/delivery/task-sync.js.map +1 -1
  176. package/dist/delivery/task-workspace.d.ts +3 -3
  177. package/dist/delivery/task-workspace.js +3 -3
  178. package/dist/generators/self-card.d.ts +65 -0
  179. package/dist/generators/self-card.d.ts.map +1 -0
  180. package/dist/generators/self-card.js +256 -0
  181. package/dist/generators/self-card.js.map +1 -0
  182. package/dist/inference/probe.js +1 -1
  183. package/dist/inference/probe.js.map +1 -1
  184. package/dist/memory/prepopulate.d.ts.map +1 -1
  185. package/dist/memory/prepopulate.js +2 -0
  186. package/dist/memory/prepopulate.js.map +1 -1
  187. package/dist/models/analytics.d.ts +32 -0
  188. package/dist/models/analytics.d.ts.map +1 -1
  189. package/dist/models/analytics.js +68 -5
  190. package/dist/models/analytics.js.map +1 -1
  191. package/dist/models/plan-readability.d.ts +55 -0
  192. package/dist/models/plan-readability.d.ts.map +1 -0
  193. package/dist/models/plan-readability.js +339 -0
  194. package/dist/models/plan-readability.js.map +1 -0
  195. package/dist/placement/admission.d.ts +59 -0
  196. package/dist/placement/admission.d.ts.map +1 -0
  197. package/dist/placement/admission.js +197 -0
  198. package/dist/placement/admission.js.map +1 -0
  199. package/dist/placement/auto.d.ts +44 -0
  200. package/dist/placement/auto.d.ts.map +1 -0
  201. package/dist/placement/auto.js +212 -0
  202. package/dist/placement/auto.js.map +1 -0
  203. package/dist/placement/enforce.d.ts +128 -0
  204. package/dist/placement/enforce.d.ts.map +1 -0
  205. package/dist/placement/enforce.js +655 -0
  206. package/dist/placement/enforce.js.map +1 -0
  207. package/dist/placement/idle.d.ts +50 -0
  208. package/dist/placement/idle.d.ts.map +1 -0
  209. package/dist/placement/idle.js +213 -0
  210. package/dist/placement/idle.js.map +1 -0
  211. package/dist/placement/ledger.d.ts +323 -0
  212. package/dist/placement/ledger.d.ts.map +1 -0
  213. package/dist/placement/ledger.js +263 -0
  214. package/dist/placement/ledger.js.map +1 -0
  215. package/dist/placement/preview.d.ts +51 -0
  216. package/dist/placement/preview.d.ts.map +1 -0
  217. package/dist/placement/preview.js +61 -0
  218. package/dist/placement/preview.js.map +1 -0
  219. package/dist/placement/probes.d.ts +47 -0
  220. package/dist/placement/probes.d.ts.map +1 -0
  221. package/dist/placement/probes.js +207 -0
  222. package/dist/placement/probes.js.map +1 -0
  223. package/dist/placement/proxy-env.d.ts +21 -0
  224. package/dist/placement/proxy-env.d.ts.map +1 -0
  225. package/dist/placement/proxy-env.js +42 -0
  226. package/dist/placement/proxy-env.js.map +1 -0
  227. package/dist/placement/registry.d.ts +1038 -0
  228. package/dist/placement/registry.d.ts.map +1 -0
  229. package/dist/placement/registry.js +274 -0
  230. package/dist/placement/registry.js.map +1 -0
  231. package/dist/policies/liveness.d.ts +6 -8
  232. package/dist/policies/liveness.d.ts.map +1 -1
  233. package/dist/policies/liveness.js +6 -25
  234. package/dist/policies/liveness.js.map +1 -1
  235. package/dist/policies/sandbox-anchor.d.ts +91 -0
  236. package/dist/policies/sandbox-anchor.d.ts.map +1 -0
  237. package/dist/policies/sandbox-anchor.js +187 -0
  238. package/dist/policies/sandbox-anchor.js.map +1 -0
  239. package/dist/policies/sandbox-grants.d.ts +131 -0
  240. package/dist/policies/sandbox-grants.d.ts.map +1 -0
  241. package/dist/policies/sandbox-grants.js +562 -0
  242. package/dist/policies/sandbox-grants.js.map +1 -0
  243. package/dist/state/manifest.d.ts +4 -2
  244. package/dist/state/manifest.d.ts.map +1 -1
  245. package/dist/state/manifest.js +32 -16
  246. package/dist/state/manifest.js.map +1 -1
  247. package/dist/utils/checkout-root.d.ts +2 -0
  248. package/dist/utils/checkout-root.d.ts.map +1 -0
  249. package/dist/utils/checkout-root.js +43 -0
  250. package/dist/utils/checkout-root.js.map +1 -0
  251. package/dist/utils/resolve-uap-bin.d.ts +5 -0
  252. package/dist/utils/resolve-uap-bin.d.ts.map +1 -0
  253. package/dist/utils/resolve-uap-bin.js +33 -0
  254. package/dist/utils/resolve-uap-bin.js.map +1 -0
  255. package/dist/utils/terminal-safe.d.ts +12 -0
  256. package/dist/utils/terminal-safe.d.ts.map +1 -0
  257. package/dist/utils/terminal-safe.js +53 -0
  258. package/dist/utils/terminal-safe.js.map +1 -0
  259. package/docs/ATTRIBUTION.md +1 -0
  260. package/docs/DASHBOARD_UPLIFT_SPEC.md +8 -8
  261. package/docs/INDEX.md +6 -5
  262. package/docs/architecture/OVERVIEW.md +15 -3
  263. package/docs/architecture/PROTOCOL.md +7 -1
  264. package/docs/architecture/adr/0006-operator-directed-model-placement.md +86 -0
  265. package/docs/architecture/adr/0007-auto-displacement-allowlist-as-standing-consent.md +79 -0
  266. package/docs/architecture/adr/0008-fleet-clients-link-out.md +92 -0
  267. package/docs/architecture/adr/0009-completion-triggered-dispatch-and-per-leaf-model-dials.md +71 -0
  268. package/docs/architecture/adr/0010-attention-queue-and-afk-auto-decider.md +159 -0
  269. package/docs/architecture/adr/0011-sandbox-grants-and-network-egress-boundary.md +219 -0
  270. package/docs/architecture/adr/0012-operator-legibility-surfaces.md +175 -0
  271. package/docs/getting-started/INSTALLATION.md +1 -1
  272. package/docs/guides/AUTOMATIC_FEATURES.md +8 -0
  273. package/docs/guides/LOCAL_MODELS.md +1 -1
  274. package/docs/guides/PROXY.md +2 -1
  275. package/docs/guides/SANDBOX.md +91 -4
  276. package/docs/integrations/MCP_ROUTER.md +1 -1
  277. package/docs/plans/model-performance-proof-options.md +176 -0
  278. package/docs/reference/API.md +1 -1
  279. package/docs/reference/CLI.md +169 -4
  280. package/docs/reference/COMPARISON.md +1 -1
  281. package/docs/reference/CONFIGURATION.md +12 -3
  282. package/docs/reference/CONFIGURATION_REFERENCE.md +269 -14
  283. package/docs/reference/DATABASE_SCHEMA.md +1 -1
  284. package/docs/reference/FEATURES.md +15 -3
  285. package/docs/reference/PATTERNS.md +1 -1
  286. package/docs/reference/PLATFORMS.md +14 -1
  287. package/docs/specs/operator-model-placement.md +1314 -0
  288. package/docs/specs/operator-model-placement.review.md +339 -0
  289. package/package.json +2 -2
  290. package/scripts/maintenance/publish-npm.sh +23 -11
  291. package/scripts/version-bump.sh +6 -51
  292. package/src/policies/enforcers/__pycache__/_common.cpython-311.pyc +0 -0
  293. package/src/policies/enforcers/__pycache__/expert_review_required.cpython-311.pyc +0 -0
  294. package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-311.pyc +0 -0
  295. package/src/policies/schemas/policies/mandatory-testing-deployment.md +2 -5
  296. package/templates/hooks/__pycache__/deliver_autoroute.cpython-311.pyc +0 -0
  297. package/templates/hooks/session-start.sh +44 -0
  298. package/tools/agents/UAP/cli.py +14 -3
  299. package/tools/agents/UAP/paths.py +50 -0
  300. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-311.pyc +0 -0
  301. package/tools/agents/scripts/anthropic_proxy.py +1386 -99
  302. package/tools/agents/scripts/toolcall_path_normalizer.py +46 -1
  303. package/tools/agents/tests/test_anthropic_proxy_streaming.py +29 -6
  304. package/tools/agents/tests/test_doubling_break.py +64 -5
  305. package/tools/agents/tests/test_error_loop_break.py +10 -4
  306. package/tools/agents/tests/test_error_loop_ignores_correctives.py +13 -2
  307. package/tools/agents/tests/test_lockstep_escalation.py +280 -9
  308. package/tools/agents/tests/test_path_containment.py +94 -0
  309. package/tools/agents/tests/test_proxy_unix_socket.py +162 -0
  310. package/tools/agents/tests/test_signature_quality.py +349 -0
  311. package/tools/agents/tests/test_uap_compliance.py +11 -1
  312. package/web/dash/core.js +1 -0
  313. package/web/dash/tab-clients.js +196 -0
  314. package/web/dash/tab-models.js +387 -8
  315. package/web/dash/tab-policies.js +87 -4
  316. package/web/dash/tabs.js +77 -73
  317. package/web/dashboard.html +6 -0
  318. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  319. package/src/policies/enforcers/__pycache__/expert_review_required.cpython-312.pyc +0 -0
  320. package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
  321. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  322. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
  323. package/web/dash/tab-agents.js +0 -21
  324. package/web/dash/tab-deliver.js +0 -12
  325. package/web/dash/tab-memory.js +0 -12
  326. package/web/dash/tab-orchestration.js +0 -21
  327. package/web/dash/tab-overview.js +0 -21
  328. package/web/dash/tab-tasks.js +0 -21
@@ -82,7 +82,7 @@
82
82
  )
83
83
  );
84
84
  var actions = el('div', { class: 'pol-actions' },
85
- el('button', { class: 'btn btn-sm', title: 'View description + prompt', onclick: function () { openDetail(p.id); } }, 'View'),
85
+ el('button', { class: 'btn btn-sm', title: 'View description + prompt', onclick: function () { openDetail(p.id, listEl); } }, 'View'),
86
86
  el('button', { class: 'btn btn-sm', title: 'Duplicate', onclick: function () { duplicate(p.id, listEl); } }, 'Duplicate'),
87
87
  el('button', {
88
88
  class: 'btn btn-sm ' + (p.isActive ? 'btn-on' : 'btn-off'),
@@ -117,8 +117,15 @@
117
117
  }).catch(function () {});
118
118
  }
119
119
 
120
- function openDetail(id) {
120
+ function openDetail(id, listEl) {
121
121
  get('/api/policy/' + id).then(function (p) {
122
+ var after = function () { if (listEl) reloadInto(listEl); };
123
+ var prioInput = el('input', { type: 'number', min: '0', step: '1', value: String(p.priority == null ? '' : p.priority), style: { width: '60px' } });
124
+ prioInput.addEventListener('change', function () {
125
+ var n = Math.max(0, Math.round(Number(prioInput.value)));
126
+ if (!isFinite(n)) return;
127
+ api('/api/policy/' + id + '/priority', { priority: n }).then(function () { toast('Priority updated', 'ok'); after(); });
128
+ });
122
129
  var body = el('div', { class: 'pol-detail' },
123
130
  el('div', { class: 'pol-badges' },
124
131
  badge(p.level, 'lvl-' + String(p.level || '').toLowerCase()),
@@ -127,10 +134,12 @@
127
134
  badge('prio ' + p.priority, 'prio'),
128
135
  badge(p.isActive ? 'enabled' : 'disabled', p.isActive ? 'on' : 'off')
129
136
  ),
137
+ el('label', { class: 'field' }, 'Priority',
138
+ prioInput),
130
139
  el('label', { class: 'field' }, 'Level',
131
- selectFor(LEVELS, p.level, function (v) { api('/api/policy/' + id + '/level', { level: v }).then(function () { toast('Level updated', 'ok'); }); })),
140
+ selectFor(LEVELS, p.level, function (v) { api('/api/policy/' + id + '/level', { level: v }).then(function () { toast('Level updated', 'ok'); after(); }); })),
132
141
  el('label', { class: 'field' }, 'Stage',
133
- selectFor(STAGES, p.stage, function (v) { api('/api/policy/' + id + '/stage', { stage: v }).then(function () { toast('Stage updated', 'ok'); }); })),
142
+ selectFor(STAGES, p.stage, function (v) { api('/api/policy/' + id + '/stage', { stage: v }).then(function () { toast('Stage updated', 'ok'); after(); }); })),
134
143
  el('div', { class: 'pol-section-label', text: 'Description' }),
135
144
  el('div', { class: 'pol-desc-full', text: p.description || '(none)' }),
136
145
  el('div', { class: 'pol-section-label', text: 'Prompt (rawMarkdown)' }),
@@ -213,6 +222,79 @@
213
222
  document.body.removeChild(input);
214
223
  }
215
224
 
225
+ // ── Compliance & Audit (restored from the inline tab this file overrides —
226
+ // the override dropped it, but the data still ships in every snapshot) ──
227
+ function fillEvents(feed) {
228
+ UAP.clear(feed);
229
+ var ev = (UAP.liveEvents || []).slice(0, 20);
230
+ if (!ev.length) { feed.appendChild(el('div', { class: 'empty', text: 'Waiting for events…' })); return; }
231
+ ev.forEach(function (e) {
232
+ if (typeof UAP.eventRow === 'function') feed.appendChild(UAP.eventRow(e));
233
+ else feed.appendChild(el('div', { class: 'audit-row', text: (e.title || e.message || '') + ' — ' + (e.detail || '') }));
234
+ });
235
+ }
236
+ UAP.onEvents(function () {
237
+ var f = document.getElementById('pol-live-events');
238
+ if (f) fillEvents(f);
239
+ });
240
+
241
+ function renderCompliance(root) {
242
+ var d = UAP.state || {};
243
+ var compliance = d.compliance || {};
244
+ var audit = d.auditTrail || [];
245
+ var cp = el('div', { class: 'panel' }, el('h2', {}, 'Compliance & Audit'));
246
+ cp.appendChild(el('h3', {}, 'Block Rate Trend'));
247
+ cp.appendChild(el('div', { class: 'chart-container chart-spark', id: 'pol-chart-block' }));
248
+ cp.appendChild(el('h3', {}, 'Failures by Mechanism'));
249
+ var fbm = compliance.failuresByMechanism || {};
250
+ var me = Object.keys(fbm).map(function (k) { return [k, fbm[k]]; }).sort(function (a, b) { return b[1] - a[1]; });
251
+ if (me.length) {
252
+ var max = Math.max.apply(null, me.map(function (x) { return x[1]; }).concat([1]));
253
+ var mb = el('div', {});
254
+ me.forEach(function (m) {
255
+ mb.appendChild(el('div', { class: 'bar-container' },
256
+ el('span', { class: 'bar-label', style: { width: '120px' }, text: m[0] }),
257
+ el('div', { class: 'bar red', style: { width: Math.round((m[1] / max) * 180) + 'px' } }),
258
+ el('span', { class: 'bar-value', text: String(m[1]) })));
259
+ });
260
+ cp.appendChild(mb);
261
+ } else cp.appendChild(el('div', { class: 'empty', text: 'No failure mechanisms' }));
262
+ cp.appendChild(el('h3', {}, 'Recent Failures'));
263
+ var rf = compliance.recentFailures || [];
264
+ var thead = el('tr', {}); ['Time', 'Policy', 'Op', 'Mechanism', 'Reason'].forEach(function (x) { thead.appendChild(el('th', { text: x })); });
265
+ var ft = el('table', {}, thead);
266
+ rf.slice(0, 10).forEach(function (f) {
267
+ var tr = el('tr', {},
268
+ el('td', { text: (f.executedAt || '').slice(11, 19) }),
269
+ el('td', { text: f.policyName || f.policyId || '-' }),
270
+ el('td', { text: f.operation || '-' }),
271
+ el('td', { text: f.defeatedMechanism || '-' }),
272
+ el('td', { text: (f.reason || '-').slice(0, 60) }));
273
+ ft.appendChild(tr);
274
+ });
275
+ cp.appendChild(rf.length ? el('div', { class: 'table-wrap' }, ft) : el('div', { class: 'empty', text: 'No recent failures' }));
276
+ cp.appendChild(el('h3', {}, 'Audit Trail'));
277
+ var at = el('div', {});
278
+ if (audit.length) {
279
+ audit.slice(0, 15).forEach(function (e) {
280
+ var ts = (typeof e.executedAt === 'string' && e.executedAt.length >= 19) ? e.executedAt.slice(11, 19) : (e.executedAt || '-');
281
+ at.appendChild(el('div', { class: 'audit-row' },
282
+ el('span', { class: 'audit-time', text: ts }),
283
+ el('span', { class: 'audit-icon ' + (e.allowed ? 'pass' : 'block'), text: e.allowed ? 'PASS' : 'BLOCK' }),
284
+ el('span', { class: 'audit-policy', text: (e.policyId || '').slice(0, 8) }),
285
+ el('span', { class: 'audit-op', text: e.operation || '' }),
286
+ el('span', { class: 'audit-reason', text: e.reason || '' })));
287
+ });
288
+ } else at.appendChild(el('div', { class: 'empty', text: 'No audit entries' }));
289
+ cp.appendChild(at);
290
+ cp.appendChild(el('h3', {}, 'Live Events'));
291
+ var feed = el('div', { class: 'event-feed', id: 'pol-live-events' });
292
+ fillEvents(feed);
293
+ cp.appendChild(feed);
294
+ root.appendChild(cp);
295
+ UAP.charts.syncSpark('pol-chart-block', d.timeSeries || [], UAP.charts.parseBR, UAP.charts.CC.blockRate, 'Block Rate %');
296
+ }
297
+
216
298
  function render(root) {
217
299
  UAP.clear(root);
218
300
  var listEl = el('div', { class: 'pol-list' });
@@ -227,6 +309,7 @@
227
309
  root.appendChild(toolbar);
228
310
  root.appendChild(listEl);
229
311
  reloadInto(listEl);
312
+ renderCompliance(root);
230
313
  }
231
314
 
232
315
  UAP.registerTab('policies', { label: 'Policies', render: render });
package/web/dash/tabs.js CHANGED
@@ -91,13 +91,49 @@
91
91
  if (ev.length) ev.forEach(function (e) { feed.appendChild(eventRow(e)); }); else feed.appendChild(empty('Waiting for events…'));
92
92
  ep.appendChild(feed); host.appendChild(ep);
93
93
  }
94
+ // Service health strip (uap doctor): fetched ON DEMAND from /api/doctor —
95
+ // the probes are bounded-but-blocking (up to ~5s each), so it never rides
96
+ // the 2s snapshot tick. Persists across update() rebuilds; Refresh re-fetches.
97
+ function doctorPanel() {
98
+ var p = panel('Service Health (uap doctor)');
99
+ p.appendChild(el('button', { class: 'btn btn-sm', style: { marginBottom: '8px' }, onclick: function () { loadDoctor(host); } }, 'Refresh'));
100
+ var host = el('div', { id: 'ov-doctor' });
101
+ host.appendChild(el('div', { class: 'muted', text: 'Probing services…' }));
102
+ p.appendChild(host);
103
+ loadDoctor(host);
104
+ return p;
105
+ }
106
+ function loadDoctor(host) {
107
+ U.clear(host);
108
+ host.appendChild(el('div', { class: 'muted', text: 'Probing services…' }));
109
+ fetch(U.API_URL + '/api/doctor').then(function (r) { return r.json(); }).then(function (rep) {
110
+ U.clear(host);
111
+ if (rep.error) { host.appendChild(el('div', { class: 'empty', text: 'doctor unavailable: ' + rep.error })); return; }
112
+ var services = rep.services || [];
113
+ if (!services.length) { host.appendChild(empty('No services declared in the capacity policy')); return; }
114
+ var cls = function (h) { return h === 'GREEN' ? 'green' : (h === 'RED' ? 'red' : (h === 'DARK' ? 'red' : '')); };
115
+ var thead = el('tr', {}); ['Service', 'Health'].forEach(function (x) { thead.appendChild(el('th', { text: x })); });
116
+ var t = el('table', {}, thead);
117
+ services.forEach(function (s) {
118
+ var tr = el('tr', {},
119
+ el('td', { text: s.name || '-' }),
120
+ el('td', {}, el('span', { class: 'badge ' + cls(s.health), text: s.health || '-' }),
121
+ (s.reasons && s.reasons.length) ? el('span', { class: 'muted', text: ' — ' + s.reasons[0] }) : null));
122
+ t.appendChild(tr);
123
+ });
124
+ host.appendChild(el('div', { class: 'table-wrap' }, t));
125
+ }).catch(function () {
126
+ U.clear(host);
127
+ host.appendChild(el('div', { class: 'empty', text: 'doctor request failed' }));
128
+ });
129
+ }
94
130
  function build(root, d) {
95
131
  var tiles = el('div', { class: 'tile-grid', id: 'ov-tiles' });
96
132
  var hero = el('div', { class: 'hero-row' },
97
133
  panelWith('Tasks & Agents', el('div', { class: 'chart-container chart-hero', id: 'ov-chart-tasks' })),
98
134
  panelWith('Compression & Memory', el('div', { class: 'chart-container chart-hero', id: 'ov-chart-comp' })));
99
135
  var summaries = el('div', { class: 'metrics-row', id: 'ov-summaries', style: { gridTemplateColumns: 'repeat(auto-fit,minmax(280px,1fr))' } });
100
- root.appendChild(tiles); root.appendChild(hero); root.appendChild(summaries);
136
+ root.appendChild(tiles); root.appendChild(hero); root.appendChild(summaries); root.appendChild(doctorPanel());
101
137
  fillTiles(tiles, d); fillSummaries(summaries, d); paintCharts(d);
102
138
  }
103
139
  function panelWith(title, node) { var p = panel(title); p.appendChild(node); return p; }
@@ -117,6 +153,8 @@
117
153
  var msg = (title && detail) ? (title + ' — ' + detail) : (title || detail || String((ev.data && JSON.stringify(ev.data)) || '').slice(0, 80));
118
154
  return el('div', { class: 'event-row' }, el('span', { class: 'event-time', text: ts }), el('span', { class: 'event-cat cat-' + cat, text: cat }), el('span', { class: 'event-msg', text: msg }));
119
155
  }
156
+ // Shared with the tab-policies.js override (it rebuilds the Live Events feed).
157
+ U.eventRow = eventRow;
120
158
 
121
159
  // ═══════════════════════════ TASKS & EPICS ═══════════════════════════
122
160
  U.registerTab('tasks', (function () {
@@ -222,7 +260,7 @@
222
260
  ]).then(function (v) { if (v && v.title) U.api('/api/tasks', { title: v.title, type: v.type, priority: Number(v.priority) || 2, assignee: v.assignee || undefined }).then(function () { U.toast('Task created', 'ok'); }); });
223
261
  } }, '+ New Task'),
224
262
  ]);
225
- var counts = el('div', { class: 'section-note', text: (tasks.total || 0) + ' tasks · ' + (tasks.done || 0) + ' done · ' + (tasks.inProgress || 0) + ' active · ' + (tasks.blocked || 0) + ' blocked · ' + (tasks.open || 0) + ' open' });
263
+ var counts = el('div', { class: 'section-note', text: (tasks.total || 0) + ' tasks · ' + (tasks.done || 0) + ' done · ' + (tasks.inProgress || 0) + ' active · ' + (tasks.blocked || 0) + ' blocked · ' + (tasks.open || 0) + ' open · board shows newest ' + (tasks.boardShown != null ? tasks.boardShown : (tasks.items || []).length) + ' of ' + (tasks.boardTotal != null ? tasks.boardTotal : (tasks.total || 0) - (tasks.done || 0)) + ' not-done' });
226
264
  p.appendChild(counts);
227
265
  p.appendChild(el('div', { class: 'toolbar' }, el('span', { class: 'label', text: 'Filter:' }), filterSel));
228
266
  var board = el('div', { class: 'kanban', id: 'kanban-board' });
@@ -259,7 +297,7 @@
259
297
  function mergeAgents(d) {
260
298
  var out = {}, order = [];
261
299
  var coord = (d.coordination && d.coordination.agents) || [];
262
- coord.forEach(function (a) { out[a.id] = { id: a.id, name: a.name, status: a.status, task: a.task || a.type || '', type: a.type || 'main' }; order.push(a.id); });
300
+ coord.forEach(function (a) { out[a.id] = { id: a.id, name: a.name, status: a.status, task: a.task || a.type || '', type: a.type || 'main', lastHeartbeat: a.lastHeartbeat || a.startedAt || null }; order.push(a.id); });
263
301
  var sess = (d.session && d.session.agents) || [];
264
302
  sess.forEach(function (a) { var e = out[a.id] || { id: a.id, name: a.name, status: a.status, task: a.task, type: a.type }; e.tokensIn = a.tokensIn; e.tokensOut = a.tokensOut; e.tokensUsed = a.tokensUsed; e.model = a.model; e.cost = a.cost; e.durationMs = a.durationMs; e.taskCount = a.taskCount; if (!out[a.id]) order.push(a.id); out[a.id] = e; });
265
303
  return order.map(function (id) { return out[id]; });
@@ -309,11 +347,22 @@
309
347
  }
310
348
  function build(root, d) {
311
349
  var agents = mergeAgents(d);
350
+ // Freshest heartbeats first — a stale agent sinks to the bottom instead
351
+ // of hiding among fresh ones.
352
+ agents.sort(function (x, y) {
353
+ var ax = x.lastHeartbeat ? new Date(x.lastHeartbeat).getTime() : 0;
354
+ var ay = y.lastHeartbeat ? new Date(y.lastHeartbeat).getTime() : 0;
355
+ return ay - ax;
356
+ });
312
357
  var ap = panel('Agents', [el('button', { class: 'btn', onclick: function () { U.confirm('Clean up stale agents (stale heartbeats)?').then(function (ok) { if (ok) U.api('/api/agents/clean', {}).then(function (r) { U.toast('Cleaned ' + ((r && r.cleaned) || 0) + ' stale agents', 'ok'); }); }); } }, 'Clean stale')]);
313
358
  if (!agents.length) ap.appendChild(empty('No agents registered'));
314
359
  else { var grid = el('div', { class: 'card-grid' }); agents.forEach(function (a) {
360
+ // Heartbeat age: agents re-beat every ~30s, so >5m (300s) is stale.
361
+ var hbAgeMs = a.lastHeartbeat ? (Date.now() - new Date(a.lastHeartbeat).getTime()) : null;
362
+ var hbAgeS = hbAgeMs != null && isFinite(hbAgeMs) ? Math.floor(hbAgeMs / 1000) : null;
363
+ var hbNode = hbAgeS == null ? null : el('span', { class: hbAgeS > 300 ? 'value red' : 'muted', text: 'hb ' + U.timeAgo(a.lastHeartbeat) + (hbAgeS > 300 ? ' (stale)' : '') });
315
364
  grid.appendChild(el('div', { class: 'entity-card', tabindex: '0', role: 'button', onclick: function () { agentDrawer(a, d); }, onkeydown: function (e) { if (e.key === 'Enter') agentDrawer(a, d); } },
316
- el('div', { class: 'ec-head' }, el('span', { class: 'badge ' + (a.type || 'main') }, a.type || 'agent'), el('span', { class: 'ec-name', text: a.name || a.id }), statusBadge(a.status)),
365
+ el('div', { class: 'ec-head' }, el('span', { class: 'badge ' + (a.type || 'main') }, a.type || 'agent'), el('span', { class: 'ec-name', text: a.name || a.id }), statusBadge(a.status), hbNode),
317
366
  el('div', { class: 'ec-task', text: a.task || '-' }),
318
367
  el('div', { class: 'ec-metrics' }, el('span', {}, fmtNum((a.tokensIn || 0) + (a.tokensOut || 0) || a.tokensUsed || 0) + ' tok'), a.model ? el('span', { class: 'muted', text: modelName(a.model) }) : null, el('span', {}, fmtUsd(a.cost || 0)))));
319
368
  }); ap.appendChild(grid); }
@@ -329,7 +378,7 @@
329
378
  return {
330
379
  label: 'Agents & Sessions',
331
380
  render: function (root, d) { build(root, d); },
332
- update: function (root, d) { var s = sig({ a: (d.coordination || {}).agents, se: (d.session || {}).agents, h: d.sessions }); if (s !== lastSig) { lastSig = s; U.clear(root); build(root, d); } },
381
+ update: function (root, d) { var s = sig({ a: (d.coordination || {}).agents, se: (d.session || {}).agents, h: d.sessions, hb: ((d.coordination || {}).agents || []).map(function (a) { return a.lastHeartbeat || ''; }) }); if (s !== lastSig) { lastSig = s; U.clear(root); build(root, d); } },
333
382
  };
334
383
  })());
335
384
 
@@ -373,7 +422,14 @@
373
422
  else shown.forEach(function (m) { tp.appendChild(node(m, 0)); });
374
423
  root.appendChild(tp);
375
424
  }
376
- return { label: 'Orchestration', render: build };
425
+ var lastSig = null;
426
+ return {
427
+ label: 'Orchestration',
428
+ render: function (root, d) { lastSig = sig({ t: d.orchestrationTree, o: d.orchestrate }); build(root, d); },
429
+ // Rebuild only on real change — the 2s tick used to wipe the tab every
430
+ // refresh (scroll/select resets) even when nothing moved.
431
+ update: function (root, d) { var s = sig({ t: d.orchestrationTree, o: d.orchestrate }); if (s === lastSig) return; lastSig = s; U.clear(root); build(root, d); },
432
+ };
377
433
  })());
378
434
 
379
435
  // ═══════════════════════════ DELIVER ═══════════════════════════
@@ -419,76 +475,24 @@
419
475
  }, function (r) { runDrawer(r); }) : empty('No deliver runs tracked yet'));
420
476
  root.appendChild(p);
421
477
  }
422
- return { label: 'Deliver', render: build };
478
+ var lastSig = null;
479
+ return {
480
+ label: 'Deliver',
481
+ render: function (root, d) { lastSig = sig(d.deliverRuns); build(root, d); },
482
+ // Rebuild only on real change — the 2s tick used to wipe the tab every
483
+ // refresh (scroll/select resets) even when no run moved.
484
+ update: function (root, d) { var s = sig(d.deliverRuns); if (s === lastSig) return; lastSig = s; U.clear(root); build(root, d); },
485
+ };
423
486
  })());
424
487
 
425
- // ═══════════════════════════ POLICIES & COMPLIANCE ═══════════════════════════
426
- U.registerTab('policies', (function () {
427
- function build(root, d) {
428
- var policies = d.policies || [], policyFiles = d.policyFiles || [], compliance = d.compliance || {}, audit = d.auditTrail || [];
429
- var pm = {}, order = [];
430
- policies.forEach(function (p) { var key = p.name || p.id; pm[key] = Object.assign({}, p, { source: 'db' }); order.push(key); });
431
- policyFiles.forEach(function (pf) { if (!pm[pf.name]) { pm[pf.name] = { id: pf.filename, name: pf.name, category: pf.category, level: '-', enforcementStage: '-', isActive: null, source: 'file' }; order.push(pf.name); } });
432
- var all = order.map(function (k) { return pm[k]; });
488
+ // NOTE: there is no inline 'policies' tab here — tab-policies.js (loaded
489
+ // after this file) owns the Policies tab outright, including the Compliance
490
+ // & Audit panel and the Live Events feed it used to shadow from here. The
491
+ // shadowed inline copy (and its duplicate U.onEvents fill of the same
492
+ // 'pol-live-events' element) was deleted (architecture review P2).
433
493
 
434
- var pp = panel('Policies');
435
- var thead = el('tr', {}); ['Name', 'Category', 'Level', 'Stage', 'Status', 'Actions'].forEach(function (x) { thead.appendChild(el('th', { text: x })); });
436
- var tbl = el('table', {}, thead);
437
- all.forEach(function (p) {
438
- var isDb = p.source === 'db';
439
- var actions = el('span', {});
440
- if (isDb) {
441
- actions.appendChild(el('button', { class: 'btn', onclick: function () { U.api('/api/policy/' + encodeURIComponent(p.id) + '/toggle', undefined).then(function (r) { U.toast('Policy ' + (r.isActive ? 'enabled' : 'disabled'), 'ok'); }); } }, p.isActive ? 'Disable' : 'Enable'));
442
- var stageSel = el('select', { onchange: function (e) { U.api('/api/policy/' + encodeURIComponent(p.id) + '/stage', { stage: e.target.value }).then(function () { U.toast('Stage: ' + e.target.value, 'ok'); }); } });
443
- ['pre-exec', 'post-exec', 'review', 'always'].forEach(function (s) { stageSel.appendChild(el('option', { value: s }, s)); }); stageSel.value = p.enforcementStage;
444
- var lvlSel = el('select', { onchange: function (e) { U.api('/api/policy/' + encodeURIComponent(p.id) + '/level', { level: e.target.value }).then(function () { U.toast('Level: ' + e.target.value, 'ok'); }); } });
445
- ['REQUIRED', 'RECOMMENDED', 'OPTIONAL'].forEach(function (l) { lvlSel.appendChild(el('option', { value: l }, l)); }); lvlSel.value = p.level;
446
- actions.appendChild(document.createTextNode(' ')); actions.appendChild(stageSel); actions.appendChild(document.createTextNode(' ')); actions.appendChild(lvlSel);
447
- } else actions.appendChild(el('span', { class: 'mono-sm', text: 'file-only' }));
448
- var status = isDb ? el('span', { class: 'badge ' + (p.isActive ? 'on' : 'off') }, p.isActive ? 'ON' : 'OFF') : el('span', { class: 'badge file-only' }, 'FILE');
449
- var tr = el('tr', {},
450
- el('td', {}, el('span', { style: { fontWeight: '500' }, text: p.name || '-' })),
451
- el('td', { text: p.category || '-' }),
452
- el('td', {}, el('span', { class: 'badge ' + (p.level || '').toLowerCase() }, p.level || '-')),
453
- el('td', { text: p.enforcementStage || '-' }),
454
- el('td', {}, status),
455
- el('td', {}, actions));
456
- tbl.appendChild(tr);
457
- });
458
- pp.appendChild(all.length ? el('div', { class: 'table-wrap' }, tbl) : empty('No policies'));
459
- root.appendChild(pp);
460
-
461
- var cp = panel('Compliance & Audit');
462
- cp.appendChild(el('h3', {}, 'Block Rate Trend'));
463
- cp.appendChild(el('div', { class: 'chart-container chart-spark', id: 'pol-chart-block' }));
464
- cp.appendChild(el('h3', {}, 'Failures by Mechanism'));
465
- var fbm = compliance.failuresByMechanism || {}; var me = Object.keys(fbm).map(function (k) { return [k, fbm[k]]; }).sort(function (a, b) { return b[1] - a[1]; });
466
- if (me.length) { var max = Math.max.apply(null, me.map(function (x) { return x[1]; }).concat([1])); var mb = el('div', {}); me.forEach(function (m) { mb.appendChild(el('div', { class: 'bar-container' }, el('span', { class: 'bar-label', style: { width: '120px' }, text: m[0] }), el('div', { class: 'bar red', style: { width: Math.round((m[1] / max) * 180) + 'px' } }), el('span', { class: 'bar-value', text: String(m[1]) }))); }); cp.appendChild(mb); }
467
- else cp.appendChild(empty('No failure mechanisms'));
468
- cp.appendChild(el('h3', {}, 'Recent Failures'));
469
- var rf = compliance.recentFailures || [];
470
- cp.appendChild(rf.length ? tableNode(['Time', 'Policy', 'Op', 'Mechanism', 'Reason'], rf.slice(0, 10), function (f) { return [el('span', { class: 'mono-sm', text: (f.executedAt || '').slice(11, 19) }), el('span', { class: 'value red', text: f.policyName || f.policyId || '-' }), f.operation || '-', el('span', { class: 'badge required' }, f.defeatedMechanism || '-'), el('span', { class: 'muted', text: (f.reason || '-').slice(0, 60) })]; }) : empty('No recent failures'));
471
- cp.appendChild(el('h3', {}, 'Audit Trail'));
472
- var at = el('div', {});
473
- if (audit.length) audit.slice(0, 15).forEach(function (e) { var ts = (typeof e.executedAt === 'string' && e.executedAt.length >= 19) ? e.executedAt.slice(11, 19) : (e.executedAt || '-'); at.appendChild(el('div', { class: 'audit-row' }, el('span', { class: 'audit-time', text: ts }), el('span', { class: 'audit-icon ' + (e.allowed ? 'pass' : 'block'), text: e.allowed ? 'PASS' : 'BLOCK' }), el('span', { class: 'audit-policy', text: (e.policyId || '').slice(0, 8) }), el('span', { class: 'audit-op', text: e.operation || '' }), el('span', { class: 'audit-reason', text: e.reason || '' }))); });
474
- else at.appendChild(empty('No audit entries'));
475
- cp.appendChild(at);
476
- cp.appendChild(el('h3', {}, 'Live Events'));
477
- var feed = el('div', { class: 'event-feed', id: 'pol-live-events' });
478
- fillEvents(feed);
479
- cp.appendChild(feed);
480
- root.appendChild(cp);
481
- U.charts.syncSpark('pol-chart-block', d.timeSeries || [], U.charts.parseBR, U.charts.CC.blockRate, 'Block Rate %');
482
- }
483
- function fillEvents(feed) {
484
- U.clear(feed);
485
- var ev = (U.liveEvents || []).slice(0, 20);
486
- if (!ev.length) { feed.appendChild(empty('Waiting for events…')); return; }
487
- ev.forEach(function (e) { feed.appendChild(eventRow(e)); });
488
- }
489
- U.onEvents(function () { var f = document.getElementById('pol-live-events'); if (f) fillEvents(f); });
490
- return { label: 'Policies', render: build };
491
- })());
494
+ // NOTE: there is likewise no inline 'clients' tab — tab-clients.js (loaded
495
+ // after this file) owns the Clients fleet panel outright (same precedent).
492
496
 
493
497
  // ═══════════════════════════ MODELS ═══════════════════════════
494
498
  U.registerTab('models', (function () {
@@ -50,5 +50,11 @@
50
50
  <!-- Loaded AFTER tabs.js so its registerTab('policies') overrides the inline
51
51
  stub with the full policy-management panel (view/duplicate/reorder/AI-order/import/export). -->
52
52
  <script src="/dash/tab-policies.js"></script>
53
+ <!-- Loaded AFTER tabs.js so registerTab('models') overrides the routing-only
54
+ stub with the full Models & Placement panel (registry, ledger, preview). -->
55
+ <script src="/dash/tab-models.js"></script>
56
+ <!-- Loaded AFTER tabs.js; owns the Clients fleet tab outright (tab-policies.js
57
+ precedent): per-client cards + spawn-on-demand link-out. -->
58
+ <script src="/dash/tab-clients.js"></script>
53
59
  </body>
54
60
  </html>
@@ -1,21 +0,0 @@
1
- /**
2
- * tab-agents.js — Stub tab: Agents
3
- * Registers with UAP.registerTab and renders a 'coming soon' message.
4
- */
5
- (function () {
6
- 'use strict';
7
-
8
- function render() {
9
- var el = document.createElement('div');
10
- el.className = 'coming-soon';
11
- el.innerHTML =
12
- '<div class="icon">🤖</div>' +
13
- '<h2>Agents</h2>' +
14
- '<p>Coming soon</p>';
15
- return el;
16
- }
17
-
18
- if (typeof UAP !== 'undefined' && typeof UAP.registerTab === 'function') {
19
- UAP.registerTab('agents', { render: render });
20
- }
21
- })();
@@ -1,12 +0,0 @@
1
- /**
2
- * tab-deliver.js — Deliver tab stub
3
- * Registers with UAP.registerTab and renders a "coming soon" message
4
- * (textContent only, per the module contract — no innerHTML).
5
- */
6
-
7
- UAP.registerTab('deliver', {
8
- label: 'Deliver',
9
- render(root, state) {
10
- root.textContent = 'Deliver — coming soon';
11
- }
12
- });
@@ -1,12 +0,0 @@
1
- /**
2
- * tab-memory.js — Memory tab stub
3
- * Registers with UAP.registerTab and renders a "coming soon" message
4
- * (textContent only, per the module contract — no innerHTML).
5
- */
6
-
7
- UAP.registerTab('memory', {
8
- label: 'Memory',
9
- render(root, state) {
10
- root.textContent = 'Memory — coming soon';
11
- }
12
- });
@@ -1,21 +0,0 @@
1
- /**
2
- * tab-orchestration.js — Stub tab: Orchestration
3
- * Registers with UAP.registerTab and renders a 'coming soon' message.
4
- */
5
- (function () {
6
- 'use strict';
7
-
8
- function render() {
9
- var el = document.createElement('div');
10
- el.className = 'coming-soon';
11
- el.innerHTML =
12
- '<div class="icon">🔄</div>' +
13
- '<h2>Orchestration</h2>' +
14
- '<p>Coming soon</p>';
15
- return el;
16
- }
17
-
18
- if (typeof UAP !== 'undefined' && typeof UAP.registerTab === 'function') {
19
- UAP.registerTab('orchestration', { render: render });
20
- }
21
- })();
@@ -1,21 +0,0 @@
1
- /**
2
- * tab-overview.js — Stub tab: Overview
3
- * Registers with UAP.registerTab and renders a 'coming soon' message.
4
- */
5
- (function () {
6
- 'use strict';
7
-
8
- function render() {
9
- var el = document.createElement('div');
10
- el.className = 'coming-soon';
11
- el.innerHTML =
12
- '<div class="icon">📊</div>' +
13
- '<h2>Overview</h2>' +
14
- '<p>Coming soon</p>';
15
- return el;
16
- }
17
-
18
- if (typeof UAP !== 'undefined' && typeof UAP.registerTab === 'function') {
19
- UAP.registerTab('overview', { render: render });
20
- }
21
- })();
@@ -1,21 +0,0 @@
1
- /**
2
- * tab-tasks.js — Stub tab: Tasks
3
- * Registers with UAP.registerTab and renders a 'coming soon' message.
4
- */
5
- (function () {
6
- 'use strict';
7
-
8
- function render() {
9
- var el = document.createElement('div');
10
- el.className = 'coming-soon';
11
- el.innerHTML =
12
- '<div class="icon">📋</div>' +
13
- '<h2>Tasks</h2>' +
14
- '<p>Coming soon</p>';
15
- return el;
16
- }
17
-
18
- if (typeof UAP !== 'undefined' && typeof UAP.registerTab === 'function') {
19
- UAP.registerTab('tasks', { render: render });
20
- }
21
- })();